feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
CI / Go Backend (push) Canceled after 0s

初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
xyou
2026-08-08 14:07:56 +08:00
parent 9500c4c80a
commit f70b061d1a
1972 changed files with 159760 additions and 6 deletions
+177
View File
@@ -0,0 +1,177 @@
# PCL.Core 项目级别的编辑器规则
[*.cs]
# 定义符号
# 常量
dotnet_naming_symbols.constants.applicable_kinds = field
dotnet_naming_symbols.constants.applicable_accessibilities = *
dotnet_naming_symbols.constants.required_modifiers = const
# 局部变量 & 方法/构造函数参数
dotnet_naming_symbols.local_and_parameter.applicable_kinds = local, parameter
dotnet_naming_symbols.local_and_parameter.applicable_accessibilities = *
dotnet_naming_symbols.local_and_parameter.required_modifiers =
# 私有实例只读字段
dotnet_naming_symbols.private_instance_readonly_fields.applicable_kinds = field
dotnet_naming_symbols.private_instance_readonly_fields.applicable_accessibilities = private
dotnet_naming_symbols.private_instance_readonly_fields.required_modifiers = readonly
# 私有静态只读字段
dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private
dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = static, readonly
# 私有实例字段(非只读)
dotnet_naming_symbols.private_instance_fields.applicable_kinds = field
dotnet_naming_symbols.private_instance_fields.applicable_accessibilities = private
dotnet_naming_symbols.private_instance_fields.required_modifiers =
# 私有静态字段(非只读)
dotnet_naming_symbols.private_static_fields.applicable_kinds = field
dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private
dotnet_naming_symbols.private_static_fields.required_modifiers = static
# 私有实例属性
dotnet_naming_symbols.private_instance_properties.applicable_kinds = property
dotnet_naming_symbols.private_instance_properties.applicable_accessibilities = private
dotnet_naming_symbols.private_instance_properties.required_modifiers =
# 私有静态属性
dotnet_naming_symbols.private_static_properties.applicable_kinds = property
dotnet_naming_symbols.private_static_properties.applicable_accessibilities = private
dotnet_naming_symbols.private_static_properties.required_modifiers = static
# 其它实例变量(public/internal/protected 的字段、属性、事件)
dotnet_naming_symbols.variables_instance_others.applicable_kinds = field, property, event
dotnet_naming_symbols.variables_instance_others.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
dotnet_naming_symbols.variables_instance_others.required_modifiers =
# 其它静态变量(public/internal/protected 的字段、属性、事件)
dotnet_naming_symbols.variables_static_others.applicable_kinds = field, property, event
dotnet_naming_symbols.variables_static_others.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
dotnet_naming_symbols.variables_static_others.required_modifiers = static
# 私有方法(静态+实例)
dotnet_naming_symbols.private_methods.applicable_kinds = method
dotnet_naming_symbols.private_methods.applicable_accessibilities = private
dotnet_naming_symbols.private_methods.required_modifiers =
# 非私有方法(静态+实例)
dotnet_naming_symbols.non_private_methods.applicable_kinds = method
dotnet_naming_symbols.non_private_methods.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
dotnet_naming_symbols.non_private_methods.required_modifiers =
# 局部方法(local function
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
dotnet_naming_symbols.local_functions.applicable_accessibilities = *
dotnet_naming_symbols.local_functions.required_modifiers =
# 接口
dotnet_naming_symbols.interfaces.applicable_kinds = interface
dotnet_naming_symbols.interfaces.applicable_accessibilities = *
dotnet_naming_symbols.interfaces.required_modifiers =
# 其它类型(class, struct, enum, delegate
dotnet_naming_symbols.other_types.applicable_kinds = class, struct, enum, delegate
dotnet_naming_symbols.other_types.applicable_accessibilities = *
dotnet_naming_symbols.other_types.required_modifiers =
# 定义命名风格
# camelCase
dotnet_naming_style.camel_case_style.capitalization = camel_case
# PascalCase
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# _PascalCase
dotnet_naming_style.underscore_pascal_case_style.capitalization = pascal_case
dotnet_naming_style.underscore_pascal_case_style.required_prefix = _
# _camelCase
dotnet_naming_style.underscore_camel_case_style.capitalization = camel_case
dotnet_naming_style.underscore_camel_case_style.required_prefix = _
# IPascalCase
dotnet_naming_style.prefix_I_pascal_style.capitalization = pascal_case
dotnet_naming_style.prefix_I_pascal_style.required_prefix = I
# 绑定命名规则
# 常量 应 PascalCase
dotnet_naming_rule.const.symbols = constants
dotnet_naming_rule.const.style = pascal_case_style
dotnet_naming_rule.const.severity = warning
# 局部变量 & 参数 应 camelCase
dotnet_naming_rule.local_var.symbols = local_and_parameter
dotnet_naming_rule.local_var.style = camel_case_style
dotnet_naming_rule.local_var.severity = warning
# 私有实例只读字段 应 _camelCase
dotnet_naming_rule.private_readonly_field.symbols = private_instance_readonly_fields
dotnet_naming_rule.private_readonly_field.style = underscore_camel_case_style
dotnet_naming_rule.private_readonly_field.severity = warning
# 私有静态只读字段 应 _PascalCase
dotnet_naming_rule.private_static_readonly_field.symbols = private_static_readonly_fields
dotnet_naming_rule.private_static_readonly_field.style = underscore_pascal_case_style
dotnet_naming_rule.private_static_readonly_field.severity = warning
# 私有实例字段 应 _camelCase
dotnet_naming_rule.private_field.symbols = private_instance_fields
dotnet_naming_rule.private_field.style = underscore_camel_case_style
dotnet_naming_rule.private_field.severity = warning
# 私有静态字段 应 _camelCase
dotnet_naming_rule.private_static_field.symbols = private_static_fields
dotnet_naming_rule.private_static_field.style = underscore_camel_case_style
dotnet_naming_rule.private_static_field.severity = warning
# 私有实例属性 应 _PascalCase
dotnet_naming_rule.private_property.symbols = private_instance_properties
dotnet_naming_rule.private_property.style = underscore_pascal_case_style
dotnet_naming_rule.private_property.severity = warning
# 私有静态属性 应 PascalCase
dotnet_naming_rule.private_static_property.symbols = private_static_properties
dotnet_naming_rule.private_static_property.style = pascal_case_style
dotnet_naming_rule.private_static_property.severity = warning
# 其它实例变量 应 PascalCase
dotnet_naming_rule.field.symbols = variables_instance_others
dotnet_naming_rule.field.style = pascal_case_style
dotnet_naming_rule.field.severity = warning
# 其它静态变量 应 PascalCase
dotnet_naming_rule.static_field.symbols = variables_static_others
dotnet_naming_rule.static_field.style = pascal_case_style
dotnet_naming_rule.static_field.severity = warning
# 私有方法 应 _PascalCase
dotnet_naming_rule.private_method.symbols = private_methods
dotnet_naming_rule.private_method.style = underscore_pascal_case_style
dotnet_naming_rule.private_method.severity = warning
# 非私有方法 应 PascalCase
dotnet_naming_rule.method.symbols = non_private_methods
dotnet_naming_rule.method.style = pascal_case_style
dotnet_naming_rule.method.severity = warning
# 局部方法 应 PascalCase
dotnet_naming_rule.local_func.symbols = local_functions
dotnet_naming_rule.local_func.style = pascal_case_style
dotnet_naming_rule.local_func.severity = warning
# 接口 应 IPascalCase
dotnet_naming_rule.interface.symbols = interfaces
dotnet_naming_rule.interface.style = prefix_I_pascal_style
dotnet_naming_rule.interface.severity = warning
# 其它类型 应 PascalCase
dotnet_naming_rule.type.symbols = other_types
dotnet_naming_rule.type.style = pascal_case_style
dotnet_naming_rule.type.severity = warning
+410
View File
@@ -0,0 +1,410 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
[Aa][Rr][Mm]64[Ee][Cc]/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
# but not Directory.Build.rsp, as it configures directory-level build defaults
!Directory.Build.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# AWS SAM Build and Temporary Artifacts folder
.aws-sam
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
.idea/
*.sln.iml
# Generated Code
**/*.g.cs
+195
View File
@@ -0,0 +1,195 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text.Json;
using System.Threading;
using System.Windows;
using PCL.Core.Logging;
using PCL.Core.Utils;
namespace PCL.Core.App;
/// <summary>
/// 基础工具集。
/// </summary>
public static class Basics
{
#region
/// <summary>
/// 启动器元数据。
/// </summary>
public static MetadataModel Metadata { get; } = JsonSerializer.Deserialize<MetadataModel>(
Assembly.GetEntryAssembly()!.GetManifestResourceStream("PCL.metadata.json")!, JsonCompat.SerializerOptions)!;
/// <summary>
/// 版本名称。
/// </summary>
public static string VersionName => Metadata.Version.BaseName;
/// <summary>
/// 版本内部代号。
/// </summary>
public static int VersionCode => Metadata.Version.Code;
/// <summary>
/// 版本分支名。
/// </summary>
public static string VersionBranch => Metadata.Version.BranchName;
/// <summary>
/// 当前日期是否为愚人节。
/// </summary>
public static bool IsAprilFool => DateTime.Now is { Month: 4, Day: 1 };
#endregion
#region
/// <summary>
/// 当前进程实例。
/// </summary>
public static Process CurrentProcess { get; } = Process.GetCurrentProcess();
/// <summary>
/// 当前进程 ID。
/// </summary>
public static int CurrentProcessId { get; } = CurrentProcess.Id;
/// <summary>
/// 当前进程可执行文件的绝对路径。
/// </summary>
public static string ExecutablePath { get; } = Environment.ProcessPath!;
/// <summary>
/// 当前进程可执行文件所在的目录。若有需求,请使用 <see cref="Path.Combine(string[])"/> 而不是自行拼接路径。
/// </summary>
public static string ExecutableDirectory { get; } = GetParentPath(ExecutablePath) ?? CurrentDirectory;
/// <summary>
/// 当前进程可执行文件的名称,含扩展名。
/// </summary>
public static string ExecutableName { get; } = Path.GetFileName(ExecutablePath);
/// <summary>
/// 当前进程可执行文件的名称,不含扩展名。
/// </summary>
public static string ExecutableNameWithoutExtension { get; } = Path.GetFileNameWithoutExtension(ExecutablePath);
/// <summary>
/// 当前进程包括第一个参数(文件名)的完整命令行参数。
/// </summary>
public static string[] FullCommandLineArguments { get; } = Environment.GetCommandLineArgs();
/// <summary>
/// 当前进程不包括第一个参数(文件名)的命令行参数。
/// </summary>
public static string[] CommandLineArguments { get; } = FullCommandLineArguments[1..];
/// <summary>
/// 实时获取的当前目录。若要在可执行文件目录中存放文件等内容,请使用更准确的 <see cref="ExecutableDirectory"/> 而不是这个目录。
/// </summary>
public static string CurrentDirectory => Environment.CurrentDirectory;
#endregion
#region 线
/// <summary>
/// 在新的工作线程运行指定委托。
/// </summary>
/// <param name="action">要运行的委托</param>
/// <param name="name">线程名,默认为 <c>WorkerThread@[ThreadId]</c></param>
/// <param name="priority">线程优先级</param>
/// <returns>新创建的线程实例</returns>
public static Thread RunInNewThread(Action action, string? name = null, ThreadPriority priority = ThreadPriority.Normal)
{
var threadName = new AtomicVariable<string>(name);
var thread = new Thread(() =>
{
try { action(); }
catch (ThreadInterruptedException) { LogWrapper.Trace("Thread", $"{threadName.Value}: 已中止"); }
catch (Exception ex) { LogWrapper.Error(ex, "Thread", $"{threadName.Value}: 抛出异常"); }
})
{ Priority = priority };
threadName.Value ??= $"Worker#{thread.ManagedThreadId}";
thread.Name = threadName.Value;
thread.Start();
return thread;
}
#endregion
#region
/// <summary>
/// 获取某个路径的父路径/目录。
/// </summary>
/// <param name="path">路径文本</param>
/// <returns>父路径文本,可能为 <c>null</c></returns>
public static string? GetParentPath(string path) => Path.GetDirectoryName(path) ?? Path.GetPathRoot(path);
/// <summary>
/// 获取某个路径的父路径/目录。
/// </summary>
/// <param name="path">路径文本</param>
/// <returns>父路径文本,或空白</returns>
public static string GetParentPathOrEmpty(string path) => GetParentPath(path) ?? string.Empty;
/// <summary>
/// 获取某个路径的父路径/目录。
/// </summary>
/// <param name="path">路径文本</param>
/// <returns>父路径文本,或默认 (<see cref="CurrentDirectory"/>)</returns>
public static string GetParentPathOrDefault(string path) => GetParentPath(path) ?? CurrentDirectory;
/// <summary>
/// 以默认方式打开一个路径 (文件或目录)
/// </summary>
/// <param name="path">路径文本</param>
/// <param name="workingDirectory">执行工作目录</param>
public static void OpenPath(string path, string? workingDirectory = null)
{
var psi = new ProcessStartInfo(path)
{
WorkingDirectory = workingDirectory ?? CurrentDirectory,
UseShellExecute = true,
CreateNoWindow = true
};
try
{
Process.Start(psi);
}
catch (Win32Exception ex) when (ex.NativeErrorCode == 1155 && File.Exists(path))
{
Process.Start(new ProcessStartInfo
{
FileName = "notepad.exe",
Arguments = $"\"{path}\"",
UseShellExecute = false
});
}
}
#endregion
#region
/// <summary>
/// 获取程序打包资源的输入流。该资源必须声明为 <c>Resource</c> 类型,否则将会报错,<c>Images</c>
/// 和 <c>Resources</c> 目录已默认声明该类型。
/// </summary>
/// <param name="path">资源路径,例如 "Resources/java-wrapper.jar"</param>
/// <returns>资源输入流,若资源不存在则为 <c>null</c></returns>
public static Stream? GetResourceStream(string path)
{
var resourceInfo = Application.GetResourceStream(new Uri($"pack://application:,,,/{path}", UriKind.Absolute));
return resourceInfo?.Stream;
}
private const string AssemblyImagePath = "pack://application:,,,/Plain Craft Launcher 2;component/Images/";
public static string GetAppImagePath(string imageName) => AssemblyImagePath + imageName;
#endregion
}
@@ -0,0 +1,8 @@
namespace PCL.Core.App.Cli;
public enum ArgumentValueKind
{
Bool,
Decimal,
Text,
}
@@ -0,0 +1,37 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace PCL.Core.App.Cli;
public class BoolArgument : CommandArgument<bool>
{
public override ArgumentValueKind ValueKind => ArgumentValueKind.Bool;
protected override bool ParseValueText()
{
var text = ValueText.ToLowerInvariant().Trim();
return text is not ("0" or "false");
}
public override bool TryCastValue<T>([NotNullWhen(true)] out T value)
{
if (base.TryCastValue(out value)) return true;
var type = typeof(T);
if (type != typeof(sbyte) &&
type != typeof(byte) &&
type != typeof(short) &&
type != typeof(ushort) &&
type != typeof(int) &&
type != typeof(uint) &&
type != typeof(long) &&
type != typeof(ulong) &&
type != typeof(nint) &&
type != typeof(nuint)) return false;
// magic code
var v = Value;
Unsafe.As<T, byte>(ref value) = Unsafe.As<bool, byte>(ref v);
#pragma warning disable CS8762 // The analyzer sucks.
return true;
#pragma warning restore CS8762
}
}
@@ -0,0 +1,92 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace PCL.Core.App.Cli;
/// <summary>
/// 无泛型的命令行参数模型
/// </summary>
/// <seealso cref="CommandArgument{TValue}"/>
public abstract class CommandArgument
{
/// <summary>
/// 参数键
/// </summary>
public required string Key { get; init; }
/// <summary>
/// 参数值文本
/// </summary>
public required string ValueText { get; init; }
/// <summary>
/// 参数值类型
/// </summary>
public abstract ArgumentValueKind ValueKind { get; }
/// <summary>
/// 尝试以指定类型获取参数值
/// </summary>
/// <param name="value">参数值,若尝试失败则为该类型默认值</param>
/// <typeparam name="T">参数值的类型</typeparam>
/// <returns>是否成功,若类型不匹配则失败</returns>
public abstract bool TryCastValue<T>([NotNullWhen(true)] out T? value);
public T? CastValue<T>()
{
var result = TryCastValue(out T? value);
return result ? value : throw new InvalidCastException("Value type mismatch or cannot cast");
}
}
/// <summary>
/// 命令行参数模型
/// </summary>
/// <typeparam name="TValue">参数值的类型</typeparam>
public abstract class CommandArgument<TValue> : CommandArgument
{
/// <summary>
/// 从参数值文本中解析参数类型
/// </summary>
/// <returns>对应类型的参数值</returns>
protected abstract TValue ParseValueText();
private bool _isValueParsed = false;
/// <summary>
/// 参数值
/// </summary>
public TValue Value
{
get
{
if (_isValueParsed) return field;
_isValueParsed = true;
return field = ParseValueText();
}
protected init
{
field = value;
_isValueParsed = true;
}
} = default!;
public override bool TryCastValue<T>([NotNullWhen(true)] out T value)
{
if (Value is T v)
{
value = v;
return true;
}
value = default!;
if (typeof(T) == typeof(string))
{
Unsafe.As<T, string>(ref value) = ValueText;
#pragma warning disable CS8762 // The analyzer sucks.
return true;
#pragma warning restore CS8762
}
return false;
}
}
@@ -0,0 +1,266 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace PCL.Core.App.Cli;
/// <summary>
/// 命令行模型
/// </summary>
[JsonConverter(typeof(CommandLineJsonConverter))]
public class CommandLine
{
/// <summary>
/// 命令文本
/// </summary>
public required string CommandText { get; init; }
/// <summary>
/// 子命令
/// </summary>
public CommandLine? Subcommand { get; init; } = null;
/// <summary>
/// 子命令文本
/// </summary>
public string? SubcommandText => Subcommand?.CommandText;
/// <summary>
/// 参数字典
/// </summary>
public required IReadOnlyDictionary<string, CommandArgument> Arguments { get; init; }
/// <summary>
/// 尝试获取参数值
/// </summary>
/// <param name="key">参数键</param>
/// <param name="value">参数值,若获取失败则为对应类型默认值</param>
/// <typeparam name="TValue">参数值的类型</typeparam>
/// <returns>是否存在该键; 存在该键时值的类型是否匹配</returns>
public (bool exists, bool isTypeMatch) TryGetArgumentValue<TValue>(string key, out TValue? value)
{
var exists = Arguments.TryGetValue(key, out var arg);
var isTypeMatch = false;
if (exists && (isTypeMatch = arg!.TryCastValue(out TValue? typedValue)))
{
value = typedValue;
return (true, true);
}
value = default;
return (exists, isTypeMatch);
}
/// <summary>
/// 解析参数数组,第一个元素会被视为主命令
/// </summary>
/// <param name="args">参数数组</param>
/// <param name="subcommands">各级子命令列表</param>
/// <returns>命令行模型实例</returns>
public static CommandLine Parse(ReadOnlySpan<string> args, IEnumerable<SubcommandDefinition>? subcommands = null)
{
subcommands ??= [];
SubcommandDefinition root = (args[0], subcommands);
return CommandLineParser.Parse(args, root);
}
public override string ToString()
{
var sb = new StringBuilder();
sb.Append(CommandText).Append(" [");
if (Arguments.Count > 0) sb.AppendLine();
foreach (var arg in Arguments.Values)
{
sb.Append(" --").Append(arg.Key);
var value = arg.ValueKind switch
{
ArgumentValueKind.Bool => arg.CastValue<bool>() ? "true" : "false",
ArgumentValueKind.Decimal => arg.CastValue<decimal>().ToString(CultureInfo.InvariantCulture),
ArgumentValueKind.Text => arg.ValueText,
_ => null
};
if (value is not null) sb.Append(": ").Append(value);
sb.AppendLine();
}
sb.Append(']');
if (Subcommand is not null) sb.AppendLine().Append("-> ").Append(Subcommand.ToString().Replace("\n", "\n "));
return sb.ToString();
}
}
file static class CommandLineParser
{
private static (CommandArgument, bool) _ParseArgument(string key, string possibleValueText)
{
if (key.StartsWith("--")) key = key[2..];
if (possibleValueText.Length == 0 || possibleValueText.StartsWith("--"))
return (new BoolArgument { Key = key, ValueText = string.Empty }, false);
if (possibleValueText.ToLowerInvariant() is "true" or "false")
return (new BoolArgument { Key = key, ValueText = possibleValueText }, true);
if (decimal.TryParse(possibleValueText, out var d))
return (new DecimalArgument { Key = key, ValueText = possibleValueText, Value = d }, true);
return (new TextArgument { Key = key, ValueText = possibleValueText }, true);
}
public static CommandLine Parse(ReadOnlySpan<string> args, SubcommandDefinition subcommands)
{
if (args.IsEmpty) throw new ArgumentException("The argument span must contain at least 1 element", nameof(args));
var i = 1;
var commandText = args[0];
var argumentList = new Dictionary<string, CommandArgument>();
CommandLine? subcommand = null;
while (i < args.Length)
{
var currentText = args[i];
if (subcommands.Contains(currentText))
{
subcommand = Parse(args[i..], subcommands.SubcommandMap[currentText]);
break;
}
var (commandArgument, hasValueText) = _ParseArgument(currentText,
(i == args.Length - 1) || (subcommands.Contains(args[i + 1])) ? "" : args[i + 1]);
argumentList[commandArgument.Key] = commandArgument;
i += hasValueText ? 2 : 1;
}
return new CommandLine
{
CommandText = commandText,
Arguments = argumentList.AsReadOnly(),
Subcommand = subcommand
};
}
}
/// <summary>
/// 用于 <see cref="CommandLine"/> 的 JSON 转换器
/// </summary>
// Generated by gpt-5.3-codex (20260218)
public sealed class CommandLineJsonConverter : JsonConverter<CommandLine>
{
public override CommandLine? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null) return null;
if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException("Expected object for CommandLine.");
string? commandText = null;
CommandLine? subcommand = null;
var arguments = new Dictionary<string, CommandArgument>();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject) break;
if (reader.TokenType != JsonTokenType.PropertyName) throw new JsonException("Expected property name.");
var propName = reader.GetString();
if (!reader.Read()) throw new JsonException("Unexpected end of json.");
switch (propName)
{
case "cmd":
commandText = reader.GetString() ?? throw new JsonException("cmd cannot be null.");
break;
case "sub":
subcommand = JsonSerializer.Deserialize<CommandLine>(ref reader, options);
break;
case "args":
_ReadArguments(ref reader, arguments);
break;
default:
// 忽略未知字段,提升前向兼容
reader.Skip();
break;
}
}
if (string.IsNullOrEmpty(commandText)) throw new JsonException("Missing required property: cmd.");
return new CommandLine
{
CommandText = commandText,
Arguments = arguments.AsReadOnly(),
Subcommand = subcommand
};
}
public override void Write(Utf8JsonWriter writer, CommandLine value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WriteString("cmd", value.CommandText);
if (value.Arguments.Count > 0)
{
writer.WritePropertyName("args");
writer.WriteStartArray();
foreach (var arg in value.Arguments.Values)
{
writer.WriteStartObject();
writer.WriteString("k", arg.Key);
writer.WriteNumber("t", (int)arg.ValueKind);
writer.WriteString("v", arg.ValueText);
writer.WriteEndObject();
}
writer.WriteEndArray();
}
if (value.Subcommand is not null)
{
writer.WritePropertyName("sub");
JsonSerializer.Serialize(writer, value.Subcommand, options);
}
writer.WriteEndObject();
}
private static void _ReadArguments(ref Utf8JsonReader reader, Dictionary<string, CommandArgument> target)
{
if (reader.TokenType != JsonTokenType.StartArray) throw new JsonException("args must be an array.");
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray) return;
if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException("Argument item must be an object.");
string? key = null;
ArgumentValueKind? kind = null;
string? valueText = null;
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject) break;
if (reader.TokenType != JsonTokenType.PropertyName) throw new JsonException("Expected argument property name.");
var name = reader.GetString();
if (!reader.Read()) throw new JsonException("Unexpected end of json.");
switch (name)
{
case "k":
key = reader.GetString();
break;
case "t":
kind = (ArgumentValueKind)reader.GetInt32();
break;
case "v":
valueText = reader.GetString() ?? string.Empty;
break;
default:
reader.Skip();
break;
}
}
if (string.IsNullOrEmpty(key)) throw new JsonException("Argument missing key(k).");
if (kind is null) throw new JsonException("Argument missing type(t).");
valueText ??= string.Empty;
CommandArgument arg = kind.Value switch
{
ArgumentValueKind.Bool => new BoolArgument { Key = key, ValueText = valueText },
ArgumentValueKind.Decimal => new DecimalArgument { Key = key, ValueText = valueText },
ArgumentValueKind.Text => new TextArgument { Key = key, ValueText = valueText },
_ => throw new JsonException($"Unsupported argument type: {(int)kind.Value}")
};
target[arg.Key] = arg;
}
throw new JsonException("args array is not closed.");
}
}
@@ -0,0 +1,47 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace PCL.Core.App.Cli;
public class DecimalArgument : CommandArgument<decimal>
{
public override ArgumentValueKind ValueKind => ArgumentValueKind.Decimal;
protected override decimal ParseValueText() => decimal.Parse(ValueText);
public new decimal Value
{
get => base.Value;
init => base.Value = value;
}
public override bool TryCastValue<T>([NotNullWhen(true)] out T value)
{
if (base.TryCastValue(out value)) return true;
var type = typeof(T);
try
{
if (type == typeof(int)) Unsafe.As<T, int>(ref value) = Convert.ToInt32(Value);
else if (type == typeof(long)) Unsafe.As<T, long>(ref value) = Convert.ToInt64(Value);
else if (type == typeof(double)) Unsafe.As<T, double>(ref value) = Convert.ToDouble(Value);
else if (type == typeof(float)) Unsafe.As<T, float>(ref value) = Convert.ToSingle(Value);
else if (type == typeof(short)) Unsafe.As<T, short>(ref value) = Convert.ToInt16(Value);
else if (type == typeof(sbyte)) Unsafe.As<T, sbyte>(ref value) = Convert.ToSByte(Value);
else if (type == typeof(ulong)) Unsafe.As<T, ulong>(ref value) = Convert.ToUInt64(Value);
else if (type == typeof(uint)) Unsafe.As<T, uint>(ref value) = Convert.ToUInt32(Value);
else if (type == typeof(ushort)) Unsafe.As<T, ushort>(ref value) = Convert.ToUInt16(Value);
else if (type == typeof(byte)) Unsafe.As<T, byte>(ref value) = Convert.ToByte(Value);
else if (type == typeof(nint)) Unsafe.As<T, nint>(ref value) = checked((nint)Convert.ToInt64(Value));
else if (type == typeof(nuint)) Unsafe.As<T, nuint>(ref value) = checked((nuint)Convert.ToUInt64(Value));
else return false;
#pragma warning disable CS8762 // The analyzer sucks.
return true;
#pragma warning restore CS8762
}
catch (Exception ex) when (ex is OverflowException or InvalidCastException or FormatException)
{
return false;
}
}
}
@@ -0,0 +1,44 @@
using System.Collections.Generic;
namespace PCL.Core.App.Cli;
public class SubcommandDefinition
{
public required string CommandText { get; init; }
public required IEnumerable<SubcommandDefinition> Subcommands { private get; init; }
public IReadOnlyDictionary<string, SubcommandDefinition> SubcommandMap
{
get
{
if (field is not null) return field;
var map = new Dictionary<string, SubcommandDefinition>();
foreach (var c in Subcommands) map[c.CommandText] = c;
return field = map.AsReadOnly();
}
} = null!;
public bool Contains(string subcommandText)
{
return SubcommandMap.ContainsKey(subcommandText);
}
public static implicit operator SubcommandDefinition((string commandText, IEnumerable<SubcommandDefinition> subcommands) tuple)
{
return new SubcommandDefinition
{
CommandText = tuple.commandText,
Subcommands = tuple.subcommands
};
}
public static implicit operator SubcommandDefinition(string commandText)
{
return new SubcommandDefinition
{
CommandText = commandText,
Subcommands = []
};
}
}
@@ -0,0 +1,8 @@
namespace PCL.Core.App.Cli;
public class TextArgument : CommandArgument<string>
{
public override ArgumentValueKind ValueKind => ArgumentValueKind.Text;
protected override string ParseValueText() => ValueText;
}
+642
View File
@@ -0,0 +1,642 @@
using PCL.Core.App.Configuration;
namespace PCL.Core.App;
/// <summary>
/// 全局配置类。
/// </summary>
// ReSharper disable InconsistentNaming
public static partial class Config
{
/// <summary>
/// 系统配置。
/// </summary>
[ConfigGroup("System")] partial class SystemConfigGroup
{
// /// <summary>
// /// 系统缓存目录。
// /// </summary>
// [ConfigItem<string>("SystemSystemCache", "")] public partial string CacheDirectory { get; set; }
/// <summary>
/// 禁用硬件加速。
/// </summary>
[ConfigItem<bool>("SystemDisableHardwareAcceleration", false)] public partial bool DisableHardwareAcceleration { get; set; }
/// <summary>
/// 遥测。
/// </summary>
[ConfigItem<bool>("SystemTelemetry", false)] public partial bool Telemetry { get; set; }
/// <summary>
/// 实时日志最大行数。
/// </summary>
[ConfigItem<int>("SystemMaxLog", 13)] public partial int MaxGameLog { get; set; }
/// <summary>
/// 动画帧率上限。
/// </summary>
[ConfigItem<int>("UiAniFPS", 59)] public partial int AnimationFpsLimit { get; set; }
}
/// <summary>
/// 网络配置。
/// </summary>
[ConfigGroup("Network")] partial class NetworkConfigGroup
{
[ConfigItem<bool>("SystemNetEnableDoH", true)] public partial bool EnableDoH { get; set; }
[ConfigGroup("HttpProxy")] partial class HttpProxyConfigGroup
{
[ConfigItem<string>("SystemHttpProxy", "", ConfigSource.SharedEncrypt)] public partial string CustomAddress { get; set; }
[ConfigItem<int>("SystemHttpProxyType", 1)] public partial int Type { get; set; }
[ConfigItem<string>("SystemHttpProxyCustomUsername", "")] public partial string CustomUsername { get; set; }
[ConfigItem<string>("SystemHttpProxyCustomPassword", "")] public partial string CustomPassword { get; set; }
}
}
/// <summary>
/// 调试配置
/// </summary>
[ConfigGroup("Debug")] partial class DebugConfigGroup
{
[ConfigItem<bool>("SystemDebugMode", false)] public partial bool Enabled { get; set; }
[ConfigItem<int>("SystemDebugAnim", 9)] public partial int AnimationSpeed { get; set; }
[ConfigItem<bool>("SystemDebugDelay", false)] public partial bool AddRandomDelay { get; set; }
[ConfigItem<bool>("SystemDebugSkipCopy", false)] public partial bool DontCopy { get; set; }
[ConfigItem<bool>("SystemDebugAllowRestrictedFeature", false)] public partial bool AllowRestrictedFeature { get; set; }
}
/// <summary>
/// 下载配置。
/// </summary>
[ConfigGroup("Download")] partial class DownloadConfigGroup
{
[ConfigItem<int>("ToolDownloadThread", 63)] public partial int ThreadLimit { get; set; }
[ConfigItem<int>("ToolDownloadSpeed", 42)] public partial int SpeedLimit { get; set; }
[ConfigItem<int>("ToolDownloadSource", 1)] public partial int FileSource { get; set; }
[ConfigItem<int>("ToolDownloadVersion", 1)] public partial int VersionListSource { get; set; }
[ConfigItem<bool>("ToolDownloadAutoSelectVersion", true)] public partial bool AutoSelectInstance { get; set; }
[ConfigItem<bool>("ToolFixAuthlib", true)] public partial bool FixAuthLib { get; set; }
/// <summary>
/// 第三方资源配置。
/// </summary>
[ConfigGroup("Comp")] partial class CompConfigGroup
{
[ConfigItem<int>("ToolDownloadTranslate", 0)] public partial int NameFormatV1 { get; set; }
[ConfigItem<int>("ToolDownloadTranslateV2", 1)] public partial int NameFormatV2 { get; set; }
[ConfigItem<bool>("ToolDownloadIgnoreQuilt", true)] public partial bool IgnoreQuilt { get; set; }
[ConfigItem<bool>("ToolDownloadAutoInstallDependencies", true)] public partial bool AutoInstallDependencies { get; set; }
[ConfigItem<bool>("ToolDownloadClipboard", false)] public partial bool ReadClipboard { get; set; }
[ConfigItem<int>("ToolDownloadMod", 1)] public partial int CompSourceSolution { get; set; }
[ConfigItem<int>("ToolModLocalNameStyle", 0)] public partial int UiCompNameSolution { get; set; }
[ConfigItem<int>("ToolDownloadQuickBehavior", 0)] public partial int QuickDownloadBehavior { get; set; }
}
}
/// <summary>
/// 工具配置。
/// </summary>
[ConfigGroup("Tool")] partial class ToolConfigGroup
{
[ConfigItem<bool>("ToolHelpChinese", true)] public partial bool AutoChangeLanguage { get; set; }
// [ConfigItem<int>("ToolUpdateAlpha", 0, ConfigSource.SharedEncrypt)] public partial int Alpha { get; set; }
[ConfigItem<bool>("ToolUpdateRelease", false)] public partial bool ReleaseNotification { get; set; }
[ConfigItem<bool>("ToolUpdateSnapshot", false)] public partial bool SnapshotNotification { get; set; }
}
/// <summary>
/// 更新配置。
/// </summary>
[ConfigGroup("Update")] partial class UpdateConfigGroup
{
/// <summary>
/// 自动更新行为。
/// </summary>
[ConfigItem<LauncherAutoUpdateBehavior>("SystemSystemUpdate", LauncherAutoUpdateBehavior.DownloadAndAnnounce, ConfigSource.Local)] public partial LauncherAutoUpdateBehavior UpdateMode { get; set; }
/// <summary>
/// 更新分支。
/// </summary>
[ConfigItem<UpdateChannel>("SystemUpdateChannel", UpdateChannel.Release, ConfigSource.Local)] public partial UpdateChannel UpdateChannel { get; set; }
/// <summary>
/// Mirror 酱 CDK。
/// </summary>
[ConfigItem<string>("SystemMirrorChyanKey", "", ConfigSource.SharedEncrypt)] public partial string MirrorChyanKey { get; set; }
}
/// <summary>
/// 联机大厅配置。
/// </summary>
[ConfigGroup("Link")] partial class LinkConfigGroup
{
/// <summary>
/// 大厅用户名。
/// </summary>
[ConfigItem<string>("LinkUsername", "")] public partial string Username { get; set; }
/// <summary>
/// 中继方式。
/// </summary>
[ConfigItem<LinkRelayBehavior>("LinkRelayType", LinkRelayBehavior.Default)] public partial LinkRelayBehavior RelayType { get; set; }
/// <summary>
/// 中继服务器类型 (社区/自有)。
/// </summary>
[ConfigItem<int>("LinkServerType", 1)] public partial int ServerType { get; set; }
/// <summary>
/// 延迟优先模式。
/// </summary>
[ConfigItem<bool>("LinkLatencyFirstMode", true)] public partial bool UseLatencyFirstMode { get; set; }
/// <summary>
/// 自定义中继服务器。
/// </summary>
[ConfigItem<string>("LinkRelayServer", "")] public partial string CustomRelayServer { get; set; }
/// <summary>
/// 传输协议优先策略。
/// </summary>
[ConfigItem<LinkProtocolPreference>("LinkProtocolPreference", LinkProtocolPreference.Tcp)] public partial LinkProtocolPreference ProtocolPreference { get; set; }
/// <summary>
/// 尝试使用端口猜测打通对称性 NAT。
/// </summary>
[ConfigItem<bool>("LinkTryPunchSym", true)] public partial bool TryPunchSym { get; set; }
/// <summary>
/// 启用 IPv6。
/// </summary>
[ConfigItem<bool>("LinkEnableIPv6", true)] public partial bool EnableIPv6 { get; set; }
/// <summary>
/// 在日志中输出 Cli 信息以用于调试。
/// </summary>
[ConfigItem<bool>("LinkEnableCliOutput", false)] public partial bool EnableCliOutput { get; set; }
}
/// <summary>
/// 个性化配置。
/// </summary>
[ConfigGroup("Preference")] partial class PreferenceConfigGroup
{
/// <summary>
/// 启动时显示 Logo。
/// </summary>
[ConfigItem<bool>("UiLauncherLogo", true, ConfigSource.Local)] public partial bool ShowStartupLogo { get; set; }
/// <summary>
/// 锁定窗口大小。
/// </summary>
[ConfigItem<bool>("UiLockWindowSize", false)] public partial bool LockWindowSize { get; set; }
/// <summary>
/// 在启动游戏时显示你知道吗。
/// </summary>
[ConfigItem<bool>("UiShowLaunchingHint", true, ConfigSource.Local)] public partial bool ShowLaunchingHint { get; set; }
/// <summary>
/// 标题内容类型。
/// </summary>
[ConfigItem<LauncherTitleType>("UiLogoType", LauncherTitleType.Default, ConfigSource.Local)] public partial LauncherTitleType WindowTitleType { get; set; }
/// <summary>
/// 窗口标题文本。
/// </summary>
[ConfigItem<string>("UiLogoText", "", ConfigSource.Local)] public partial string WindowTitleCustomText { get; set; }
/// <summary>
/// 导航栏居左。
/// </summary>
[ConfigItem<bool>("UiLogoLeft", false, ConfigSource.Local)] public partial bool TopBarLeftAlign { get; set; }
/// <summary>
/// 全局字体。
/// </summary>
[ConfigItem<string>("UiFont", "", ConfigSource.Local)] public partial string Font { get; set; }
/// <summary>
/// MOTD 字体。
/// </summary>
[ConfigItem<string>("UiMotdFont", "", ConfigSource.Local)] public partial string MotdFont { get; set; }
/// <summary>
/// 详细实例分类。
/// </summary>
[ConfigItem<bool>("DetailedInstanceClassification", false, ConfigSource.Local)] public partial bool DetailedInstanceClassification { get; set; }
/// <summary>
/// 本地化配置。
/// </summary>
[ConfigGroup("Localization", ConfigSource.Local)] partial class LocalizationConfigGroup
{
/// <summary>
/// UI 语言。auto 表示跟随系统语言。
/// </summary>
[ConfigItem<string>("UiLanguage", "auto")] public partial string Language { get; set; }
/// <summary>
/// UI 展示格式所使用的区域性。auto 表示跟随系统区域格式。
/// </summary>
[ConfigItem<string>("UiFormatCulture", "auto")] public partial string FormatCulture { get; set; }
/// <summary>
/// 区域覆盖。auto 表示自动判断。
/// </summary>
[ConfigItem<string>("UiRegion", "auto")] public partial string Region { get; set; }
}
/// <summary>
/// 界面主题配置。
/// </summary>
[ConfigGroup("Theme")] partial class ThemeConfigGroup
{
/// <summary>
/// 配色主题模式。
/// </summary>
[ConfigItem<ColorMode>("UiDarkMode", ColorMode.System)] public partial ColorMode ColorMode { get; set; }
/// <summary>
/// 暗色配色主题。
/// </summary>
[ConfigItem<ColorTheme>("UiDarkColor", ColorTheme.CatBlue)] public partial ColorTheme DarkColor { get; set; }
/// <summary>
/// 亮色配色主题。
/// </summary>
[ConfigItem<ColorTheme>("UiLightColor", ColorTheme.CatBlue)] public partial ColorTheme LightColor { get; set; }
/// <summary>
/// 窗口透明度。
/// </summary>
[ConfigItem<int>("UiLauncherTransparent", 600, ConfigSource.Local)] public partial int WindowOpacity { get; set; }
/// <summary>
/// 自定义主题:色相 (H)。
/// </summary>
[ConfigItem<int>("UiLauncherHue", 180, ConfigSource.Local)] public partial int WindowHue { get; set; }
/// <summary>
/// 自定义主题:饱和度 (S)。
/// </summary>
[ConfigItem<int>("UiLauncherSat", 80, ConfigSource.Local)] public partial int WindowSat { get; set; }
/// <summary>
/// 自定义主题:明度 (L)。
/// </summary>
[ConfigItem<int>("UiLauncherLight", 20, ConfigSource.Local)] public partial int WindowLight { get; set; }
/// <summary>
/// 自定义主题:色相渐变。
/// </summary>
[ConfigItem<int>("UiLauncherDelta", 90, ConfigSource.Local)] public partial int WindowDelta { get; set; }
/// <summary>
/// 传说中的主题选择,但是没卵用。
/// </summary>
[ConfigItem<int>("UiLauncherTheme", 0, ConfigSource.Local)] public partial int ThemeSelected { get; set; }
}
/// <summary>
/// 背景内容。
/// </summary>
[ConfigGroup("Background")] partial class BackgroundConfigGroup
{
/// <summary>
/// 彩色底部填充。
/// </summary>
[ConfigItem<bool>("UiBackgroundColorful", true, ConfigSource.Local)] public partial bool BackgroundColorful { get; set; }
/// <summary>
/// 透明度。
/// </summary>
[ConfigItem<int>("UiBackgroundOpacity", 1000, ConfigSource.Local)] public partial int WallpaperOpacity { get; set; }
/// <summary>
/// 旋转。
/// </summary>
[ConfigItem<int>("UiBackgroundCarousel", 1000, ConfigSource.Local)] public partial int WallpaperCarousel { get; set; }
/// <summary>
/// 模糊遮罩。
/// </summary>
[ConfigItem<int>("UiBackgroundBlur", 0, ConfigSource.Local)] public partial int WallpaperBlurRadius { get; set; }
/// <summary>
/// 内容裁剪模式。
/// </summary>
[ConfigItem<int>("UiBackgroundSuit", 0, ConfigSource.Local)] public partial int WallpaperSuitMode { get; set; }
/// <summary>
/// 视频自动暂停。
/// </summary>
[ConfigItem<bool>("UiAutoPauseVideo", true, ConfigSource.Local)] public partial bool AutoPauseVideo { get; set; }
}
/// <summary>
/// 高级材质。
/// </summary>
[ConfigGroup("Blur")] partial class BlurConfigGroup
{
/// <summary>
/// 是否启用。
/// </summary>
[ConfigItem<bool>("UiBlur", false, ConfigSource.Local)] public partial bool IsEnabled { get; set; }
/// <summary>
/// 模糊半径。
/// </summary>
[ConfigItem<int>("UiBlurValue", 16, ConfigSource.Local)] public partial int Radius { get; set; }
/// <summary>
/// 采样率。
/// </summary>
[ConfigItem<int>("UiBlurSamplingRate", 70, ConfigSource.Local)] public partial int SamplingRate { get; set; }
/// <summary>
/// 模糊方法。
/// </summary>
[ConfigItem<int>("UiBlurType", 0, ConfigSource.Local)] public partial int KernelType { get; set; }
}
/// <summary>
/// 自定义主页。
/// </summary>
[ConfigGroup("Homepage")] partial class HomepageConfigGroup
{
/// <summary>
/// 主页来源类型。
/// </summary>
[ConfigItem<int>("UiCustomType", 0, ConfigSource.Local)] public partial int Type { get; set; }
/// <summary>
/// 预设选项。
/// </summary>
[ConfigItem<int>("UiCustomPreset", 0, ConfigSource.Local)] public partial int SelectedPreset { get; set; }
/// <summary>
/// 自定义 URL。
/// </summary>
[ConfigItem<string>("UiCustomNet", "", ConfigSource.Local)] public partial string CustomUrl { get; set; }
}
/// <summary>
/// 背景音乐。
/// </summary>
[ConfigGroup("Music")] partial class MusicConfigGroup
{
/// <summary>
/// 音量。
/// </summary>
[ConfigItem<int>("UiMusicVolume", 500, ConfigSource.Local)] public partial int Volume { get; set; }
/// <summary>
/// 启动游戏后自动暂停。
/// </summary>
[ConfigItem<bool>("UiMusicStop", false, ConfigSource.Local)] public partial bool StopInGame { get; set; }
/// <summary>
/// 启动游戏后自动开始播放。
/// </summary>
[ConfigItem<bool>("UiMusicStart", false, ConfigSource.Local)] public partial bool StartInGame { get; set; }
/// <summary>
/// 自动开始播放。
/// </summary>
[ConfigItem<bool>("UiMusicAuto", true, ConfigSource.Local)] public partial bool StartOnStartup { get; set; }
/// <summary>
/// 随机播放。
/// </summary>
[ConfigItem<bool>("UiMusicRandom", true, ConfigSource.Local)] public partial bool ShufflePlayback { get; set; }
/// <summary>
/// 启用 SMTC。
/// </summary>
[ConfigItem<bool>("UiMusicSMTC", true, ConfigSource.Local)] public partial bool EnableSMTC { get; set; }
}
/// <summary>
/// 功能隐藏。
/// </summary>
[ConfigGroup("Hide")]
partial class HideConfigGroup
{
// 主页面
[ConfigItem<bool>("UiHiddenPageDownload", false, ConfigSource.Local)] public partial bool PageDownload { get; set; }
[ConfigItem<bool>("UiHiddenPageSetup", false, ConfigSource.Local)] public partial bool PageSetup { get; set; }
[ConfigItem<bool>("UiHiddenPageTools", false, ConfigSource.Local)] public partial bool PageTools { get; set; }
// 子页面 设置
[ConfigItem<bool>("UiHiddenSetupLaunch", false, ConfigSource.Local)] public partial bool SetupLaunch { get; set; }
[ConfigItem<bool>("UiHiddenSetupUi", false, ConfigSource.Local)] public partial bool SetupUi { get; set; }
[ConfigItem<bool>("UiHiddenSetupLauncherLanguage", false, ConfigSource.Local)] public partial bool SetupLauncherLanguage { get; set; }
[ConfigItem<bool>("UiHiddenSetupLauncherMisc", false, ConfigSource.Local)] public partial bool SetupLauncherMisc { get; set; }
[ConfigItem<bool>("UiHiddenSetupGameManage", false, ConfigSource.Local)] public partial bool SetupGameManage { get; set; }
[ConfigItem<bool>("UiHiddenSetupJava", false, ConfigSource.Local)] public partial bool SetupJava { get; set; }
[ConfigItem<bool>("UiHiddenSetupUpdate", false, ConfigSource.Local)] public partial bool SetupUpdate { get; set; }
[ConfigItem<bool>("UiHiddenSetupGameLink", false, ConfigSource.Local)] public partial bool SetupGameLink { get; set; } // 新增
[ConfigItem<bool>("UiHiddenSetupAbout", false, ConfigSource.Local)] public partial bool SetupAbout { get; set; } // 修正名称
[ConfigItem<bool>("UiHiddenSetupFeedback", false, ConfigSource.Local)] public partial bool SetupFeedback { get; set; } // 修正名称
[ConfigItem<bool>("UiHiddenSetupLog", false, ConfigSource.Local)] public partial bool SetupLog { get; set; } // 修正名称
// 子页面 工具
[ConfigItem<bool>("UiHiddenToolsGameLink", false, ConfigSource.Local)] public partial bool ToolsGameLink { get; set; } // 新增
[ConfigItem<bool>("UiHiddenToolsTest", false, ConfigSource.Local)] public partial bool ToolsTest { get; set; } // 新增
// 子页面 实例设置
[ConfigItem<bool>("UiHiddenVersionEdit", false, ConfigSource.Local)] public partial bool InstanceEdit { get; set; }
[ConfigItem<bool>("UiHiddenVersionExport", false, ConfigSource.Local)] public partial bool InstanceExport { get; set; }
[ConfigItem<bool>("UiHiddenVersionSave", false, ConfigSource.Local)] public partial bool InstanceSave { get; set; }
[ConfigItem<bool>("UiHiddenVersionScreenshot", false, ConfigSource.Local)] public partial bool InstanceScreenshot { get; set; }
[ConfigItem<bool>("UiHiddenVersionMod", false, ConfigSource.Local)] public partial bool InstanceMod { get; set; }
[ConfigItem<bool>("UiHiddenVersionResourcePack", false, ConfigSource.Local)] public partial bool InstanceResourcePack { get; set; }
[ConfigItem<bool>("UiHiddenVersionShader", false, ConfigSource.Local)] public partial bool InstanceShader { get; set; }
[ConfigItem<bool>("UiHiddenVersionSchematic", false, ConfigSource.Local)] public partial bool InstanceSchematic { get; set; }
[ConfigItem<bool>("UiHiddenVersionServer", false, ConfigSource.Local)] public partial bool InstanceServer { get; set; }
// 特定功能
[ConfigItem<bool>("UiHiddenFunctionSelect", false, ConfigSource.Local)] public partial bool FunctionSelect { get; set; }
[ConfigItem<bool>("UiHiddenFunctionModUpdate", false, ConfigSource.Local)] public partial bool FunctionModUpdate { get; set; }
[ConfigItem<bool>("UiHiddenFunctionHidden", false, ConfigSource.Local)] public partial bool FunctionHidden { get; set; }
}
}
/// <summary>
/// 启动配置。
/// </summary>
[ConfigGroup("Launch")] partial class LaunchConfigGroup
{
/// <summary>
/// 内存分配模式。
/// </summary>
[ConfigItem<int>("LaunchRamType", 0, ConfigSource.Local)] public partial int MemoryAllocationMode { get; set; }
/// <summary>
/// 自定义内存分配大小。
/// </summary>
[ConfigItem<int>("LaunchRamCustom", 15, ConfigSource.Local)] public partial int CustomMemorySize { get; set; }
/// <summary>
/// 是否固定堆大小:启用后额外追加 -Xms 并使其等于 -Xmx,隐式禁用内存归还以降低延迟抖动、利于 ZGC。见 #3282。
/// </summary>
[ConfigItem<bool>("LaunchAdvanceLockMemory", false, ConfigSource.Local)] public partial bool LockMemory { get; set; }
/// <summary>
/// 优先 IP 协议栈。
/// </summary>
[ConfigItem<JvmPreferredIpStack>("LaunchPreferredIpStack", JvmPreferredIpStack.Default)] public partial JvmPreferredIpStack PreferredIpStack { get; set; }
/// <summary>
/// 附加 JVM 参数。
/// </summary>
[ConfigItem<string>("LaunchAdvanceJvm", "-XX:+UseG1GC -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -Djdk.lang.Process.allowAmbiguousCommands=true -Dfml.ignoreInvalidMinecraftCertificates=True -Dfml.ignorePatchDiscrepancies=True -Dlog4j2.formatMsgNoLookups=true", ConfigSource.Local)] public partial string JvmArgs { get; set; }
/// <summary>
/// 附加游戏参数。
/// </summary>
[ConfigItem<string>("LaunchAdvanceGame", "", ConfigSource.Local)] public partial string GameArgs { get; set; }
/// <summary>
/// 预启动指令。
/// </summary>
[ConfigItem<string>("LaunchAdvanceRun", "", ConfigSource.Local)] public partial string PreLaunchCommand { get; set; }
/// <summary>
/// 是否等待预启动指令完成。
/// </summary>
[ConfigItem<bool>("LaunchAdvanceRunWait", true, ConfigSource.Local)] public partial bool PreLaunchCommandWait { get; set; }
/// <summary>
/// 禁用 Java Launch Wrapper。
/// </summary>
[ConfigItem<bool>("LaunchAdvanceDisableJLW", true, ConfigSource.Local)] public partial bool DisableJlw { get; set; }
/// <summary>
/// 禁用 LegacyFix
/// </summary>
[ConfigItem<bool>("LaunchAdvanceDisableLF", false, ConfigSource.Local)] public partial bool DisableLF { get; set; }
/// <summary>
/// 强制使用高性能显卡。
/// </summary>
[ConfigItem<bool>("LaunchAdvanceGraphicCard", true)] public partial bool SetGpuPreference { get; set; }
/// <summary>
/// 使用 java 而不是 javaw。
/// </summary>
[ConfigItem<bool>("LaunchAdvanceNoJavaw", false)] public partial bool NoJavaw { get; set; }
/// <summary>
/// 禁用 LWJGL Unsafe Agent。
/// </summary>
[ConfigItem<bool>("LaunchAdvanceDisableLwjglUnsafeAgent", false)] public partial bool DisableLwjglUnsafeAgent { get; set; }
/// <summary>
/// 禁用自动崩溃分析。
/// </summary>
[ConfigItem<bool>("LaunchAdvanceDisableCrashAnalysis", false, ConfigSource.Local)] public partial bool DisableCrashAnalysis { get; set; }
/// <summary>
/// 渲染器。
/// </summary>
[ConfigItem<int>("LaunchAdvanceRenderer", 0 ,ConfigSource.Local)] public partial int Renderer { get; set; }
/// <summary>
/// 游戏窗口标题。
/// </summary>
[ConfigItem<string>("LaunchArgumentTitle", "", ConfigSource.Local)] public partial string Title { get; set; }
/// <summary>
/// 自定义左下角版本信息。
/// </summary>
[ConfigItem<string>("LaunchArgumentInfo", "PCLCE", ConfigSource.Local)] public partial string TypeInfo { get; set; }
/// <summary>
/// 选择的默认 Java 实例。
/// </summary>
[ConfigItem<string>("LaunchArgumentJavaSelect", "")] public partial string SelectedJava { get; set; }
/// <summary>
/// 版本隔离 V2。
/// </summary>
[ConfigItem<int>("LaunchArgumentIndieV2", 4, ConfigSource.Local)] public partial int IndieSolutionV2 { get; set; }
/// <summary>
/// 游戏启动后启动器可见性。
/// </summary>
[ConfigItem<LauncherVisibility>("LaunchArgumentVisible", LauncherVisibility.DoNothing)] public partial LauncherVisibility LauncherVisibility { get; set; }
/// <summary>
/// 游戏进程优先级。
/// </summary>
[ConfigItem<GameProcessPriority>("LaunchArgumentPriority", GameProcessPriority.Normal)] public partial GameProcessPriority ProcessPriority { get; set; }
/// <summary>
/// 游戏窗口宽度。
/// </summary>
[ConfigItem<int>("LaunchArgumentWindowWidth", 854, ConfigSource.Local)] public partial int GameWindowWidth { get; set; }
/// <summary>
/// 游戏窗口高度。
/// </summary>
[ConfigItem<int>("LaunchArgumentWindowHeight", 480, ConfigSource.Local)] public partial int GameWindowHeight { get; set; }
/// <summary>
/// 游戏窗口模式 (正常/最大化/全屏)。
/// </summary>
[ConfigItem<GameWindowSizeMode>("LaunchArgumentWindowType", GameWindowSizeMode.Default, ConfigSource.Local)] public partial GameWindowSizeMode GameWindowMode { get; set; }
/// <summary>
/// 正版登录方式。
/// </summary>
[ConfigItem<int>("LoginMsAuthType", 1)] public partial int LoginMsAuthType { get; set; }
}
/// <summary>
/// 实例独立配置。<br/>
/// 懒得写注释了,自己理解吧。
/// </summary>
[ConfigGroup("Instance", ConfigSource.GameInstance)] partial class InstanceConfigGroup
{
[ConfigItem<string>("VersionAdvanceJvm", "")] public partial ArgConfig<string> JvmArgs { get; }
[ConfigItem<string>("VersionAdvanceGame", "")] public partial ArgConfig<string> GameArgs { get; }
[ConfigItem<int>("VersionAdvanceRenderer", 0)] public partial ArgConfig<int> Renderer { get; }
[ConfigItem<int>("VersionAdvanceAssets", 0)] public partial ArgConfig<int> AssetVerifySolutionV1 { get; }
[ConfigItem<bool>("VersionAdvanceAssetsV2", false)] public partial ArgConfig<bool> DisableAssetVerifyV2 { get; }
[ConfigItem<bool>("VersionAdvanceJava", false)] public partial ArgConfig<bool> IgnoreJavaCompatibility { get; }
[ConfigItem<bool>("VersionAdvanceDisableJlw", false)] public partial ArgConfig<bool> DisableJlwObsolete { get; }
[ConfigItem<string>("VersionAdvanceRun", "")] public partial ArgConfig<string> PreLaunchCommand { get; }
[ConfigItem<string>("VersionAdvanceClasspathHead", "")] public partial ArgConfig<string> ClasspathHead { get; }
[ConfigItem<bool>("VersionAdvanceRunWait", true)] public partial ArgConfig<bool> PreLaunchCommandWait { get; }
[ConfigItem<bool>("VersionAdvanceDisableJLW", false)] public partial ArgConfig<bool> DisableJlw { get; }
[ConfigItem<bool>("VersionAdvanceDisableLwjglUnsafeAgent", false)] public partial ArgConfig<bool> DisableLwjglUnsafeAgent { get; }
[ConfigItem<bool>("VersionAdvanceUseProxyV2", false)] public partial ArgConfig<bool> UseProxy { get; }
[ConfigItem<bool>("VersionAdvanceDisableLF", false)] public partial ArgConfig<bool> DisableLF { get; }
[ConfigItem<bool>("VersionUseDebugLog4j2Config", false)] public partial ArgConfig<bool> UseDebugLof4j2Config { get; }
[ConfigItem<int>("VersionRamType", 2)] public partial ArgConfig<int> MemorySolution { get; }
[ConfigItem<int>("VersionRamCustom", 15)] public partial ArgConfig<int> CustomMemorySize { get; }
[ConfigItem<string>("VersionArgumentTitle", "")] public partial ArgConfig<string> Title { get; }
[ConfigItem<bool>("VersionArgumentTitleEmpty", false)] public partial ArgConfig<bool> UseGlobalTitle { get; }
[ConfigItem<string>("VersionArgumentInfo", "")] public partial ArgConfig<string> TypeInfo { get; }
[ConfigItem<int>("VersionArgumentIndie", -1)] public partial ArgConfig<int> IndieV1 { get; }
[ConfigItem<bool>("VersionArgumentIndieV2", false)] public partial ArgConfig<bool> IndieV2 { get; }
[ConfigItem<string>("VersionArgumentJavaSelect", "使用全局设置")] public partial ArgConfig<string> SelectedJava { get; }
[ConfigItem<string>("VersionServerEnter", "")] public partial ArgConfig<string> ServerToEnter { get; }
}
/// <summary>
/// 实例独立配置的认证部分
/// </summary>
[ConfigGroup("InstanceAuth", ConfigSource.GameInstance)] partial class InstanceAuthConfigGroup
{
[ConfigItem<int>("VersionServerLoginRequire", 0)] public partial ArgConfig<int> LoginRequirementSolution { get; }
[ConfigItem<string>("VersionServerAuthRegister", "")] public partial ArgConfig<string> AuthRegisterAddress { get; }
[ConfigItem<string>("VersionServerAuthName", "")] public partial ArgConfig<string> AuthServerDisplayName { get; }
[ConfigItem<string>("VersionServerAuthServer", "")] public partial ArgConfig<string> AuthServerAddress { get; }
[ConfigItem<bool>("VersionServerLoginLock", false)] public partial ArgConfig<bool> AuthLocked { get; }
}
}
+116
View File
@@ -0,0 +1,116 @@
namespace PCL.Core.App;
/// <summary>
/// 联机协议偏好
/// </summary>
public enum LinkProtocolPreference
{
Tcp,
Udp
}
/// <summary>
/// 主题模式(亮/暗/系统)
/// </summary>
public enum ColorMode
{
Light = 0,
Dark = 1,
System = 2
}
/// <summary>
/// 配色主题
/// </summary>
public enum ColorTheme
{
SkyBlue = 0,
CatBlue = 1,
DeathBlue = 2,
HmclBlue = 3
}
/// <summary>
/// 更新通道
/// </summary>
public enum UpdateChannel
{
Release = 0,
Beta = 1,
Dev = 2
}
/// <summary>
/// 游戏窗口大小模式
/// </summary>
public enum GameWindowSizeMode
{
Fullscreen = 0,
Default = 1,
Launcher = 2,
Custom = 3,
Maximized = 4
}
/// <summary>
/// 游戏进程优先级
/// </summary>
public enum GameProcessPriority
{
AboveNormal = 0,
Normal = 1,
BelowNormal = 2,
High = 3,
RealTime = 4
}
/// <summary>
/// 游戏启动后启动器可见性
/// </summary>
public enum LauncherVisibility
{
ExitImmediately = 0,
ObsoleteCaseDoNotUse = 1,
HideAndExit = 2,
HideAndReopen = 3,
MinimizeAndReopen = 4,
DoNothing = 5
}
/// <summary>
/// JVM 优先 IP 栈类型
/// </summary>
public enum JvmPreferredIpStack
{
PreferV4 = 0,
Default = 1,
PreferV6 = 2
}
/// <summary>
/// 联机中继行为
/// </summary>
public enum LinkRelayBehavior
{
Default = 0,
ForceRelay = 1
}
/// <summary>
/// 启动器更新行为
/// </summary>
public enum LauncherAutoUpdateBehavior
{
DownloadAndInstall = 0,
DownloadAndAnnounce = 1,
AnnounceOnly = 2,
Disable = 3
}
public enum LauncherTitleType
{
None = 0,
Default = 1,
Text = 2,
Image = 3
}
@@ -0,0 +1,13 @@
using System;
using PCL.Core.Utils;
namespace PCL.Core.App.Configuration;
public class ArgConfig<TValue> : ParameterizedProperty<object, TValue>
{
public ArgConfig(Func<object?, TValue> getter, Action<object?, TValue> setter)
{
GetValue = getter;
SetValue = setter;
}
}
@@ -0,0 +1,40 @@
using System;
namespace PCL.Core.App.Configuration;
#pragma warning disable CS9113 // Parameter is unread.
/// <summary>
/// 标记一个 partial 属性,以添加对应配置项并自动生成访问器。
/// </summary>
/// <param name="key">配置键</param>
/// <param name="defaultValue">默认值</param>
/// <param name="source">配置来源</param>
/// <typeparam name="TValue">值类型</typeparam>
[AttributeUsage(AttributeTargets.Property)]
public sealed class ConfigItemAttribute<TValue>(string key, TValue? defaultValue, ConfigSource source = default) : Attribute;
/// <summary>
/// 标记一个 partial 属性,以添加对应配置项并自动生成访问器。
/// <p>注意:默认值将调用指定类型的无参构造器来获取,以解决 C# attribute 在 2025 年仍然不支持隔壁 JVM 在 2015
/// 年就支持的极其先进的自定义类型参数的问题。因此,值的类型必须有公开的无参构造器,否则运行时将会抛出异常。</p>
/// </summary>
/// <param name="key">配置键</param>
/// <param name="source">配置来源</param>
/// <typeparam name="TValue">值类型</typeparam>
[AttributeUsage(AttributeTargets.Property)]
public sealed class AnyConfigItemAttribute<TValue>(string key, ConfigSource source = default) : Attribute;
/// <summary>
/// 标记一个 partial 类为配置组,以自动实现 <see cref="IConfigScope"/> 并生成对应的作用域检查方法。
/// </summary>
/// <param name="name">组名,需符合 C# 标识符规范</param>
/// <param name="source">组级别的默认配置来源</param>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class ConfigGroupAttribute(string name, ConfigSource source = default) : Attribute;
/// <summary>
/// 标记一个类型为 <see cref="ConfigEventRegistry"/> 的 public static 属性,以注册配置项事件。
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public sealed class RegisterConfigEventAttribute : Attribute;
@@ -0,0 +1,60 @@
using System;
namespace PCL.Core.App.Configuration;
/// <summary>
/// 配置项事件。
/// </summary>
[Flags]
public enum ConfigEvent
{
/// <summary>
/// 初始化,当且仅当程序初始化时调用一次。
/// </summary>
Init = 0b00001,
/// <summary>
/// 获取。
/// </summary>
Get = 0b00010,
/// <summary>
/// 设置值。
/// </summary>
Set = 0b00100,
/// <summary>
/// 重置值。
/// </summary>
Reset = 0b01000,
/// <summary>
/// 检查是否为默认值。
/// </summary>
CheckDefault = 0b10000,
/// <summary>
/// 保留备用。
/// </summary>
None = 0,
/// <summary>
/// 所有读取操作。
/// </summary>
Read = Get | CheckDefault,
/// <summary>
/// 所有更新操作。
/// </summary>
Update = Set | Reset,
/// <summary>
/// 所有改变操作。
/// </summary>
Changed = Init | Update,
/// <summary>
/// 所有操作。没事别监听这个,一点风吹草动都会触发它。
/// </summary>
All = Read | Changed
}
@@ -0,0 +1,32 @@
namespace PCL.Core.App.Configuration;
/// <summary>
/// 配置项事件参数。
/// </summary>
/// <param name="Item">配置项。</param>
/// <param name="Event">触发事件。</param>
/// <param name="Argument">上下文参数。</param>
/// <param name="OldValue">旧值。</param>
/// <param name="NewValue">新值。</param>
public record ConfigEventArgs(
ConfigItem Item,
ConfigEvent Event,
object? Argument,
object? OldValue,
object? NewValue
) {
/// <summary>
/// 设置一个新值代替原来的值进行对应操作,只在 <see cref="ConfigObserver.IsPreview"/> 为 <c>true</c> 时有效。
/// </summary>
public object? NewValueReplacement { get; set; } = null;
/// <summary>
/// 是否取消事件,只在 <see cref="ConfigObserver.IsPreview"/> 为 <c>true</c> 时有效。
/// </summary>
public bool Cancelled { get; set; } = false;
/// <summary>
/// 配置当前的值。
/// </summary>
public object? Value => NewValueReplacement ?? NewValue;
}
@@ -0,0 +1,7 @@
namespace PCL.Core.App.Configuration;
/// <summary>
/// 配置项监听委托。
/// </summary>
/// <param name="e">事件参数</param>
public delegate void ConfigEventHandler(ConfigEventArgs e);
@@ -0,0 +1,24 @@
using System.Collections.Generic;
namespace PCL.Core.App.Configuration;
public class ConfigEventRegistry(
IEnumerable<IConfigScope> scope,
ConfigEventHandler handler,
ConfigEvent trigger = ConfigEvent.Changed,
bool isPreview = false)
{
public IEnumerable<IConfigScope> Scopes => scope;
public ConfigEvent Trigger => trigger;
public ConfigEventHandler Handler => handler;
public bool IsPreview => isPreview;
public ConfigEventRegistry(
IConfigScope scope,
ConfigEventHandler handler,
ConfigEvent trigger = ConfigEvent.Changed,
bool isPreview = false
) : this([scope], handler, trigger, isPreview) { }
public ConfigObserver ToObserver() => new(trigger, handler, isPreview);
}
@@ -0,0 +1,369 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using PCL.Core.Utils.Exts;
namespace PCL.Core.App.Configuration;
/// <summary>
/// 配置项。
/// </summary>
/// <typeparam name="TValue">值类型</typeparam>
public class ConfigItem<TValue>(
string key,
Func<TValue> defaultValue,
ConfigSource source
) : IConfigScope, ConfigItem
{
public string Key { get; } = key;
public ConfigSource Source { get; } = source;
public Type Type => typeof(TValue);
private Func<TValue>? _defaultValueConstructor = defaultValue;
private TValue? _defaultValue;
private bool _defaultValueHasSet = false;
#region
private TValue _GetDefaultValue()
{
if (_defaultValueHasSet) return _defaultValue!;
_defaultValue = _defaultValueConstructor!();
_defaultValueHasSet = true;
_defaultValueConstructor = null;
return _defaultValue;
}
/// <summary>
/// 默认值。
/// </summary>
public TValue DefaultValue => _GetDefaultValue();
public object DefaultValueNoType => DefaultValue ?? default!;
#endregion
public ConfigItem(string key, TValue defaultValue, ConfigSource source)
: this(key, () => defaultValue, source) { }
public IEnumerable<string> CheckScope(IReadOnlySet<string> keys) => keys.Contains(Key) ? [Key] : [];
#region
private IConfigProvider _Provider { get => field ??= ConfigService.GetProvider(Source); } = null!;
private ConfigValueCache<TValue> _valueCache = new();
/// <summary>
/// 指定是否启用缓存。<br/>
/// <b>NOTE</b>: 禁用缓存将造成一些功能(如自动监听内容更改)不按预期工作,请仅在真正需要的时候禁用。
/// </summary>
public bool EnableCache
{
get;
set
{
if (!field) _valueCache.InvalidateAll();
field = value;
}
} = true;
/// <summary>
/// 处理看起来是新的值,并返回是否真的是新的。<br/>
/// 只有启用缓存时该方法才会生效,未启用缓存将始终直接返回 <see langword="true"/>。
/// </summary>
private bool _ProcessNewCache(TValue newCache, object? argument, bool force = false)
{
if (!EnableCache) return true;
if (!force)
{
// 判断是否是新值
var existsOld = _valueCache.TryRead(out var oldCache, argument);
if (existsOld && EqualityComparer<TValue>.Default.Equals(oldCache, newCache)) return false;
}
// 对新缓存值执行准备工作
if (newCache is INotifyPropertyChanged reactive)
reactive.PropertyChanged += (_, _) => OnContentChanged();
else if (newCache is INotifyCollectionChanged reactiveCollection)
reactiveCollection.CollectionChanged += (_, _) => OnContentChanged();
// 写入缓存
_valueCache.Write(newCache, argument);
return true;
void OnContentChanged() => SetValue(newCache, argument, bypassCache: true);
}
/// <summary>
/// 获取配置值。
/// </summary>
/// <param name="argument">上下文参数</param>
/// <returns>已设置的配置值或默认值</returns>
public TValue GetValue(object? argument = null)
{
TValue? value = default; // 这个初始化是多余的,但是煞笔巨硬不初始化会报错
var exists = EnableCache && _valueCache.TryRead(out value, argument);
var newValue = false;
if (!exists)
{
newValue = true;
exists = _Provider.GetValue(Key, out value, argument);
}
var e = _TriggerEvent(ConfigEvent.Get, argument, value, true);
if (e is not null)
{
if (e.Cancelled) return DefaultValue;
if (e.NewValueReplacement is not null) return (TValue)e.NewValueReplacement;
}
if (!exists) value = DefaultValue;
if (newValue) _ProcessNewCache(value!, argument);
return value!;
}
public object GetValueNoType(object? argument = null)
{
return GetValue(argument) ?? default!;
}
/// <summary>
/// 设置配置值。
/// </summary>
/// <param name="value">用于设置的值</param>
/// <param name="argument">上下文参数</param>
/// <param name="forceNewValue">强制将传入的值视为新值,不检查缓存,仅在 <see cref="EnableCache"/> 为 <see langword="true"/> 时生效</param>
/// <param name="bypassCache">跳过缓存检查和写入,相当于对本次操作临时将 <see cref="EnableCache"/> 设为 <see langword="false"/></param>
/// <returns>是否成功设置值,若成功则为 <c>true</c></returns>
public bool SetValue(TValue value, object? argument = null, bool forceNewValue = false, bool bypassCache = false)
{
var e = _TriggerEvent(ConfigEvent.Set, argument, value, isPreview: true);
if (e is not null)
{
if (e.Cancelled) return false;
if (e.NewValueReplacement is not null) value = (TValue)e.NewValueReplacement;
}
if (bypassCache || _ProcessNewCache(value, argument, forceNewValue))
_Provider.SetValue(Key, value, argument);
_TriggerEvent(ConfigEvent.Set, argument, value, e: e, isPreview: false);
return true;
}
public bool SetValueNoType(object value, object? argument = null)
{
try
{
return SetValue((TValue)value, argument);
}
catch (InvalidCastException)
{
// 兼容龙猫妙妙小代码直接传入 string 值的行为
if (value is string v) return SetValue(v.Convert<TValue>()!, argument);
var msg = $"Value convert failed (required: {Type.FullName}, provided: {value.GetType().FullName})";
throw new InvalidCastException(msg);
}
}
public bool SetDefaultValue(object? argument = null, bool? forceNewValue = null)
{
return SetValue(DefaultValue, argument, forceNewValue ?? IsDefault(argument));
}
public bool Reset(object? argument = null)
{
var e = _TriggerEvent(ConfigEvent.Reset, argument, null, isPreview: true);
if (e is { Cancelled: true }) return false;
_Provider.Delete(Key, argument);
if (EnableCache) _valueCache.Invalidate(argument);
_TriggerEvent(ConfigEvent.Reset, argument, DefaultValueNoType, isPreview: false);
return true;
}
public bool IsDefault(object? argument = null)
{
var result = !_Provider.Exists(Key, argument);
var e = _TriggerEvent(ConfigEvent.CheckDefault, argument, result);
if (e is { NewValueReplacement: not null }) result = (bool)e.NewValueReplacement;
return result;
}
#endregion
#region
private readonly HashSet<ConfigObserver> _observers = [];
private readonly HashSet<ConfigObserver> _previewObservers = [];
public void Observe(ConfigObserver observer)
{
if (observer.IsPreview) _previewObservers.Add(observer);
else _observers.Add(observer);
}
public bool Unobserve(ConfigObserver observer)
=> observer.IsPreview ? _previewObservers.Remove(observer) : _observers.Remove(observer);
// 获取值,若未设置则返回 null
private object? _GetValueOrNull(object? argument)
{
var exists = _Provider.GetValue<TValue>(Key, out var value, argument);
return exists ? value : null;
}
public ConfigEventArgs? TriggerEvent(
ConfigEvent trigger, object? argument,
bool bypassOldValue = false, bool fillNewValue = false)
{
return _TriggerEvent(trigger, argument, null, bypassOldValue, fillNewValue);
}
private ConfigEventArgs? _TriggerEvent(
ConfigEvent trigger, object? argument, object? newValue,
bool bypassOldValue = false, bool fillNewValue = false,
ConfigEventArgs? e = null, bool? isPreview = null)
{
var replaceNewValue = false;
foreach (var observer in (
from observer in (isPreview is { } p ? (p ? _previewObservers : _observers) : _previewObservers.Concat(_observers))
let logic = (int)observer.Event & (int)trigger
where logic > 0
select observer
)) {
if (e is null)
{
if (isPreview == false && !bypassOldValue) bypassOldValue = true;
var currentValue = (fillNewValue || !bypassOldValue) ? _GetValueOrNull(argument) : null;
if (newValue is null && fillNewValue) newValue = currentValue ?? DefaultValue;
e = new ConfigEventArgs(this, trigger, argument, bypassOldValue ? null : currentValue, newValue);
}
observer.Handler(e);
// 对 preview 的特殊处理
if (observer.IsPreview)
{
if (e.NewValueReplacement is not null) replaceNewValue = true; // 记录替换操作
if (e.Cancelled) return e;
}
// 防止非 preview 事件传递替换值
else if (!replaceNewValue && e.NewValueReplacement is not null) e.NewValueReplacement = null;
}
// 防止非 preview 事件传递取消状态
if (e is { Cancelled: true }) e.Cancelled = false;
return e;
}
#endregion
}
/// <summary>
/// <see cref="ConfigItem{TValue}"/> 的非泛型方法抽象层,用于手动解决巨硬
/// 2025 年仍未支持的极其先进的隐式去泛型化。
/// </summary>
// ReSharper disable once InconsistentNaming
public interface ConfigItem
{
/// <summary>
/// 配置键。
/// </summary>
public string Key { get; }
/// <summary>
/// 配置来源。
/// </summary>
public ConfigSource Source { get; }
/// <summary>
/// 配置的 CLR 类型。
/// </summary>
public Type Type { get; }
/// <summary>
/// 传入事件观察器以观察事件。
/// </summary>
public void Observe(ConfigObserver observer);
/// <summary>
/// 取消观察事件。
/// </summary>
public bool Unobserve(ConfigObserver observer);
/// <summary>
/// 触发配置项事件。
/// </summary>
/// <param name="trigger">触发事件</param>
/// <param name="argument">上下文参数</param>
/// <param name="bypassOldValue">若为 <c>true</c> 则向事件参数的旧值传递 <c>null</c>,否则传递当前值</param>
/// <param name="fillNewValue">若为 <c>true</c>,当新值为 <c>null</c> 时将传递当前值或默认值</param>
/// <returns></returns>
public ConfigEventArgs? TriggerEvent(
ConfigEvent trigger,
object? argument,
bool bypassOldValue = false,
bool fillNewValue = false
);
/// <summary>
/// 传入事件类型与处理委托以观察事件。
/// </summary>
public ConfigObserver Observe(ConfigEvent trigger, ConfigEventHandler handler, bool isPreview = false)
{
var observer = new ConfigObserver(trigger, handler, isPreview);
Observe(observer);
return observer;
}
/// <summary>
/// 传统的用于兼容的值改变事件。<br/>
/// 请尽可能避免使用,而是使用 <see cref="RegisterConfigEventAttribute"/>
/// 来声明事件观察,或使用 <see cref="Observe(ConfigObserver)"/> 和
/// <see cref="Unobserve(ConfigObserver)"/> 来灵活管理事件。
/// </summary>
public event ConfigEventHandler Changed
{
add => Observe(ConfigEvent.Changed, value);
remove => throw new NotSupportedException("Please use Observe() and Unobserve() to access advanced event management");
}
/// <summary>
/// 重置配置值,使其变为未设置状态。
/// </summary>
/// <param name="argument">上下文参数</param>
/// <returns>是否成功重置值,若成功则为 <c>true</c></returns>
public bool Reset(object? argument = null);
/// <summary>
/// 检查配置值是否为默认值 (未设置状态)
/// </summary>
/// <param name="argument">上下文参数</param>
public bool IsDefault(object? argument = null);
/// <summary>
/// 将配置项的值设置为默认值,设置后 <see cref="IsDefault"/> 将返回 <c>false</c>。
/// </summary>
/// <param name="argument">上下文参数</param>
/// <param name="forceNewValue">强制视为新值,不检查缓存,仅在 <see cref="EnableCache"/> 为 <see langword="true"/> 时生效</param>
/// <returns>是否成功设置值,若成功则为 <c>true</c></returns>
public bool SetDefaultValue(object? argument = null, bool? forceNewValue = null);
/// <summary>
/// 没有泛型的 <see cref="ConfigItem{T}.GetValue"/>。<br/>
/// 我们都不想给非引用类型装箱,但是龙猫想。
/// </summary>
public object GetValueNoType(object? argument = null);
/// <summary>
/// 没有泛型的 <see cref="ConfigItem{T}.SetValue"/>。<br/>
/// 我们都不想给非引用类型装箱,但是龙猫想。
/// </summary>
public bool SetValueNoType(object value, object? argument = null);
/// <summary>
/// 没有泛型的 <see cref="ConfigItem{T}.DefaultValue"/>。<br/>
/// 我们都不想给非引用类型装箱,但是龙猫想。
/// </summary>
public object DefaultValueNoType { get; }
/// <summary>
/// 是否启用值缓存,默认为 <c>true</c>。设为 <c>false</c> 将清除已存在的缓存。
/// </summary>
public bool EnableCache { get; set; }
}
@@ -0,0 +1,178 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
namespace PCL.Core.App.Configuration;
public delegate void ConfigMigrationHandler(string from, string to);
/// <summary>
/// 配置文件迁移模型与工具。
/// </summary>
public class ConfigMigration
{
/// <summary>
/// 来源路径。
/// </summary>
public required string From { get; init; }
/// <summary>
/// 目标路径。
/// </summary>
public required string To { get; init; }
/// <summary>
/// 优先级 (或称"权重"),数字越大越容易被使用。
/// </summary>
public int Priority { get; init; } = 0;
/// <summary>
/// 迁移实现。
/// </summary>
public required ConfigMigrationHandler OnMigration { get; init; }
/// <summary>
/// 执行配置文件迁移。
/// </summary>
/// <param name="target">最终目标路径</param>
/// <param name="migrations">可用的迁移过程</param>
/// <returns>若找到最简方案并迁移成功,则为 <c>true</c>,否则为 <c>false</c></returns>
public static bool Migrate(string target, IEnumerable<ConfigMigration> migrations)
{
migrations = migrations as ConfigMigration[] ?? migrations.ToArray();
IEnumerable<ConfigMigration>? solution = null;
var found = (
from migration in migrations.Reverse()
let path = migration.From
where File.Exists(path) && _TryFindShortestPath(path, target, migrations, out solution)
select path
).Any();
if (!found) return false;
foreach (var migration in solution!) migration.OnMigration(migration.From, migration.To);
return true;
}
// 寻找最短路径
// Partly generated by gpt-5 (20250904)
// ReSharper disable InvertIf, ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
private static bool _TryFindShortestPath(string start, string end,
IEnumerable<ConfigMigration> paths, [NotNullWhen(true)] out IEnumerable<ConfigMigration>? result)
{
// 起点即终点:最短过程为 0 条边
if (start == end)
{
result = [];
return true;
}
// 构建邻接表(有向图)
var adj = new Dictionary<string, List<ConfigMigration>>(StringComparer.Ordinal);
foreach (var p in paths)
{
if (!adj.TryGetValue(p.From, out var list))
{
list = [];
adj[p.From] = list;
}
list.Add(p);
}
// 第一阶段:BFS 计算从 start 到各点的最短边数 dist
var dist = new Dictionary<string, int>(StringComparer.Ordinal);
var queue = new Queue<string>();
dist[start] = 0;
queue.Enqueue(start);
while (queue.Count > 0)
{
var u = queue.Dequeue();
if (!adj.TryGetValue(u, out var outgoing))
continue;
var du = dist[u];
foreach (var e in outgoing)
{
var v = e.To;
if (!dist.ContainsKey(v))
{
dist[v] = du + 1;
queue.Enqueue(v);
}
}
}
// 不可达
if (!dist.ContainsKey(end))
{
result = null;
return false;
}
// 将节点按深度分层,便于第二阶段的 DP
var nodesByDepth = new Dictionary<int, List<string>>();
foreach (var kv in dist)
{
if (!nodesByDepth.TryGetValue(kv.Value, out var list))
{
list = [];
nodesByDepth[kv.Value] = list;
}
list.Add(kv.Key);
}
// 第二阶段:在所有最短路径子图上,选择累计 Priority 之和最大的路径
var bestPriority = new Dictionary<string, long>(StringComparer.Ordinal); // 累计优先级和
var prevNode = new Dictionary<string, string>(StringComparer.Ordinal); // 重建路径
var prevEdge = new Dictionary<string, ConfigMigration>(StringComparer.Ordinal);
bestPriority[start] = 0;
var maxDepth = dist[end];
for (var d = 0; d < maxDepth; d++)
{
if (!nodesByDepth.TryGetValue(d, out var layer))
continue;
foreach (var u in layer)
{
if (!bestPriority.ContainsKey(u)) continue;
if (!adj.TryGetValue(u, out var outgoing)) continue;
foreach (var e in outgoing)
{
if (!dist.TryGetValue(e.To, out var dv) || dv != d + 1)
continue; // 只考虑保持最短性的边
var candidate = bestPriority[u] + e.Priority;
if (!bestPriority.TryGetValue(e.To, out var cur) || candidate > cur)
{
bestPriority[e.To] = candidate;
prevNode[e.To] = u;
prevEdge[e.To] = e;
}
}
}
}
// 重建从 start 到 end 的路径(边序列)
if (!prevEdge.ContainsKey(end)) { // 理论上不应发生
result = null;
return false;
}
var path = new List<ConfigMigration>();
var curr = end;
while (curr != start)
{
var edge = prevEdge[curr];
path.Add(edge);
curr = prevNode[curr];
}
path.Reverse();
result = path;
return true;
}
// ReSharper restore InvertIf, ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
}
@@ -0,0 +1,13 @@
namespace PCL.Core.App.Configuration;
/// <summary>
/// 配置事件观察器。
/// </summary>
/// <param name="Event">观察的事件。</param>
/// <param name="Handler">事件处理委托。</param>
/// <param name="IsPreview">指定是否预览事件处理,预览事件可覆盖原有值或取消事件处理过程。</param>
public record ConfigObserver(
ConfigEvent Event,
ConfigEventHandler Handler,
bool IsPreview = false
);
@@ -0,0 +1,364 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using PCL.Core.App.Configuration.Storage;
using PCL.Core.App.Localization;
using PCL.Core.App.IoC;
using PCL.Core.Logging;
using PCL.Core.Utils.Exts;
namespace PCL.Core.App.Configuration;
/// <summary>
/// 全局配置服务。
/// </summary>
[LifecycleService(LifecycleState.Loading, Priority = 1919810)]
[LifecycleScope("config", "配置")]
public sealed partial class ConfigService
{
private static readonly Dictionary<string, ConfigItem> _Items = [];
private static readonly HashSet<string> _KeySet = [];
/// <summary>
/// 配置键的集合。
/// </summary>
public static IReadOnlySet<string> KeySet => _KeySet;
/// <summary>
/// 全局配置文件的版本号。
/// </summary>
[ConfigItem<int>("FileVersion", 1)] public static partial int SharedVersion { get; set; }
/// <summary>
/// 本地配置文件的版本号。
/// </summary>
[ConfigItem<int>("LocalFileVersion", 1, ConfigSource.Local)] public static partial int LocalVersion { get; set; }
/// <summary>
/// 全局共享配置文件路径。
/// </summary>
public static string SharedConfigPath { get; } = Path.Combine(Paths.SharedData, "config.v1.json");
/// <summary>
/// 本地配置文件路径。
/// </summary>
public static string LocalConfigPath { get; } = Path.Combine(Paths.Data, "config.v1.yml");
#region Getters & Setters
/// <summary>
/// 尝试获取无泛型的配置项。
/// </summary>
/// <param name="key">配置键</param>
/// <param name="item">返回可观察对象</param>
/// <returns>若配置键存在,则为 <c>true</c>,否则为 <c>false</c></returns>
public static bool TryGetConfigItemNoType(string key, [NotNullWhen(true)] out ConfigItem? item)
=> _Items.TryGetValue(key, out item);
/// <summary>
/// 尝试获取配置项。
/// </summary>
/// <param name="key">配置键</param>
/// <param name="item">返回配置项,若类型不匹配则为 <c>null</c></param>
/// <typeparam name="TValue">配置项的值类型</typeparam>
/// <returns>若配置键存在,则为 <c>true</c>,否则为 <c>false</c></returns>
/// <exception cref="InvalidOperationException">配置项尚未初始化完成</exception>
public static bool TryGetConfigItem<TValue>(string key, out ConfigItem<TValue>? item)
{
if (!_isConfigItemsInitialized) throw new InvalidOperationException("Not initialized");
var result = TryGetConfigItemNoType(key, out var value);
item = result ? (value as ConfigItem<TValue>) : null;
return result;
}
/// <summary>
/// 获取配置项。
/// </summary>
/// <param name="key">配置键</param>
/// <typeparam name="TValue">配置项的值类型</typeparam>
/// <returns>配置项实例</returns>
/// <exception cref="InvalidOperationException">配置项尚未初始化完成</exception>
/// <exception cref="KeyNotFoundException">配置键不存在</exception>
/// <exception cref="InvalidCastException">值类型参数与实际类型不匹配</exception>
public static ConfigItem<TValue> GetConfigItem<TValue>(string key)
{
var result = TryGetConfigItem<TValue>(key, out var item);
if (!result) throw new KeyNotFoundException($"Config key not found: '{key}'");
return item ?? throw new InvalidCastException($"Type of '{key}' is incompatible with {typeof(TValue).FullName}");
}
/// <summary>
/// 按键设置配置值,自动处理类型匹配。若键不存在则静默失败。
/// </summary>
/// <param name="key">配置键</param>
/// <param name="value">配置值</param>
/// <param name="argument">上下文参数(实例路径等)</param>
public static void TrySetValue(string key, object value, object? argument = null)
{
if (!TryGetConfigItemNoType(key, out var item)) return;
if (item.Type.IsEnum && value is not string)
item.SetValueNoType(Enum.ToObject(item.Type, value), argument);
else
item.SetValueNoType(value, argument);
}
/// <summary>
/// 向指定作用域批量注册事件观察器。
/// </summary>
/// <param name="scope"><see cref="IConfigScope"/> 实例</param>
/// <param name="observer">观察器实例</param>
public static void RegisterObserver(IConfigScope scope, ConfigObserver observer)
{
var itemKeys = scope.CheckScope(KeySet);
foreach (var key in itemKeys)
{
var item = _Items[key];
item.Observe(observer);
}
}
#endregion
#region Providers
private static ConfigStorage? _sharedConfigProvider;
private static ConfigStorage? _sharedEncryptedConfigProvider;
private static ConfigStorage? _localConfigProvider;
private static ConfigStorage? _instanceConfigProvider;
/// <summary>
/// 获取配置提供方。
/// </summary>
/// <param name="source">来源定义</param>
/// <returns>提供方实例</returns>
/// <exception cref="InvalidOperationException">配置提供方尚未初始化完成</exception>
/// <exception cref="ArgumentException">来源定义无效</exception>
public static IConfigProvider GetProvider(ConfigSource source)
{
if (!_isProvidersInitialized) throw new InvalidOperationException("Not initialized");
return source switch
{
ConfigSource.Shared => _sharedConfigProvider!,
ConfigSource.SharedEncrypt => _sharedEncryptedConfigProvider!,
ConfigSource.Local => _localConfigProvider!,
ConfigSource.GameInstance => _instanceConfigProvider!,
_ => throw new ArgumentException($"Invalid source: {source}")
};
}
private static void _InitializeProviders()
{
Action[] inits = [
() => // shared config file
{
// try migrate
if (!File.Exists(SharedConfigPath))
{
string[] oldPaths = [
Path.Combine(Paths.OldSharedData, "Config.json"),
Path.Combine(Paths.SharedData, "config.json")
];
_TryMigrate(SharedConfigPath, oldPaths.Select(path =>
new ConfigMigration { From = path, To = SharedConfigPath, OnMigration = SharedJsonMigration }));
}
// load
var fileProvider = new JsonFileProvider(SharedConfigPath);
var storage = new FileConfigStorage(fileProvider);
_sharedConfigProvider = storage;
_sharedEncryptedConfigProvider = new EncryptedFileConfigStorage(storage);
},
() => // local config file
{
// try migrate
if (!File.Exists(LocalConfigPath)) _TryMigrate(LocalConfigPath, [
new ConfigMigration
{
From = Path.Combine(Paths.Data, "setup.ini"),
To = LocalConfigPath,
OnMigration = CatIniMigration
}
]);
// load
var fileProvider = new YamlFileProvider(LocalConfigPath);
_localConfigProvider = new FileConfigStorage(fileProvider);
},
() => // instance config file(s)
{
_instanceConfigProvider = new DynamicCacheConfigStorage
{
StorageFactory = argument =>
{
ArgumentNullException.ThrowIfNull(argument);
var dir = Path.GetFullPath(argument.ToString()!);
var configPath = Path.Combine(dir, "PCL", "config.v1.yml");
if (!File.Exists(dir)) _TryMigrate(dir, [
new ConfigMigration
{
From = Path.Combine(dir, "PCL", "setup.ini"),
To = configPath,
OnMigration = CatIniMigration
}
]);
var fileProvider = new YamlFileProvider(configPath);
var storage = new FileConfigStorage(fileProvider);
return storage;
}
};
}
];
try { Task.WaitAll(inits.Select(Task.Run).ToArray()); }
catch (AggregateException ex) { throw ex.GetBaseException(); }
return;
void SharedJsonMigration(string from, string to)
{
File.Copy(from, to);
}
void CatIniMigration(string from, string to)
{
var lines = File.ReadAllLines(from);
var yamlProvider = new YamlFileProvider(to);
foreach (var line in lines)
{
if (line.IsNullOrWhiteSpace()) continue;
var kv = line.Split(':', 2);
if (kv.Length != 2) continue;
yamlProvider.Set(kv[0], kv[1]);
}
yamlProvider.Sync();
}
}
private static void _TryMigrate(string target, IEnumerable<ConfigMigration> migrations)
{
Context.Info($"Try migrating config: {target}");
try
{
var result = ConfigMigration.Migrate(target, migrations);
if (!result) Context.Info("No migration solution available");
}
catch (Exception ex)
{
Context.Warn("Migration failed", ex);
}
}
#endregion
#region Lifecycle & Initialization
/// <summary>
/// 配置服务是否已加载完成。未加载完成时,调用与配置项相关的方法可能会抛出 <see cref="InvalidOperationException"/>。
/// </summary>
public static bool IsInitialized { get; private set; } = false;
private static bool _isProvidersInitialized = false;
private static bool _isConfigItemsInitialized = false;
[LifecycleStart]
private static void _Start()
{
if (IsInitialized) return;
#if TRACE
var timer = new Stopwatch();
timer.Start();
#endif
Context.Info("Config initialization started");
try
{
Context.Trace("Initializing config items...");
_InitializeConfigItems();
Context.Debug($"Finished initialize {_Items.Count} item(s)");
_isConfigItemsInitialized = true;
Context.Trace("Initializing providers...");
_InitializeProviders();
_isProvidersInitialized = true;
Context.Trace("Initializing observers...");
_InitializeObservers();
Context.Info("Invoking init events...");
foreach (var (_, item) in _Items)
{
item.TriggerEvent(ConfigEvent.Init, null, true, true);
}
IsInitialized = true;
}
catch (Exception ex)
{
var currentSection = _isConfigItemsInitialized ? "OBSERVER" :
_isProvidersInitialized ? "CONFIG_ITEM" : "PROVIDER";
string msg;
#if DEBUG
msg = Lang.Text("Config.Error.LoadFailed.DebugMessage", currentSection);
#else
if (ex is ConfigFileInitException e)
{
var filePath = e.Path;
var backupPath = e.Path + ".failbackup";
var bakPath = e.Path + ".bak";
File.Move(filePath, backupPath, true);
if (File.Exists(bakPath)) File.Copy(bakPath, filePath, true);
msg = Lang.Text(
"Config.Error.InvalidFormat.RecoveryMessage",
currentSection,
filePath,
backupPath);
}
else
{
msg = Lang.Text("Config.Error.LoadFailed.Message", currentSection);
}
#endif
Context.Fatal(msg, ex);
}
#if TRACE
timer.Stop();
Context.Info($"Config initialization finished in {timer.ElapsedMilliseconds} ms");
#endif
}
[LifecycleStop]
private static void _Stop()
{
// 检测是否初始化出错
if (Lifecycle.GetServiceLastException(Service.Identifier) is { } ex)
{
Context.Fatal(Lang.Text("Config.Error.LoadFailed.Title"), ex);
return;
}
Context.Info("Saving config...");
// 停止物流中心并释放资源
_sharedConfigProvider?.Stop();
_localConfigProvider?.Stop();
_instanceConfigProvider?.Stop();
}
[RegisterConfigEvent]
public static ConfigEventRegistry SharedVersionInit => new(
SharedVersionConfig,
trigger: ConfigEvent.Init,
handler: e => _UpdateConfigVersion(SharedVersionConfig, "全局", (int)e.NewValue!)
);
[RegisterConfigEvent]
public static ConfigEventRegistry LocalVersionInit => new(
scope: LocalVersionConfig,
trigger: ConfigEvent.Init,
handler: e => _UpdateConfigVersion(LocalVersionConfig, "本地", (int)e.NewValue!)
);
private static void _UpdateConfigVersion(ConfigItem<int> versionConfig, string name, int fileVersion)
{
var targetVersion = versionConfig.DefaultValue;
var isUnset = versionConfig.IsDefault();
LogWrapper.Info($"{name}配置: 文件版本 {(isUnset ? "UNSET" : fileVersion)}, 目标版本 {targetVersion}");
if (isUnset || targetVersion != fileVersion) versionConfig.SetValue(targetVersion);
}
#endregion
}
@@ -0,0 +1,27 @@
namespace PCL.Core.App.Configuration;
/// <summary>
/// 配置来源。
/// </summary>
public enum ConfigSource
{
/// <summary>
/// 全局共享配置。
/// </summary>
Shared,
/// <summary>
/// 加密的全局共享配置。
/// </summary>
SharedEncrypt,
/// <summary>
/// 本地配置。
/// </summary>
Local,
/// <summary>
/// 游戏实例特定配置。
/// </summary>
GameInstance
}
@@ -0,0 +1,86 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
namespace PCL.Core.App.Configuration;
public struct ConfigValueCache<TValue>()
{
private TValue? _cachedValue;
private bool _hasCachedValue = false;
private readonly ConcurrentDictionary<object, TValue> _cacheWithContext = [];
/// <summary>
/// 检查指定上下文参数的缓存是否存在。
/// </summary>
/// <param name="argument">上下文参数</param>
public bool Exists(object? argument = null)
{
return (argument is null) ? _hasCachedValue : _cacheWithContext.ContainsKey(argument);
}
/// <summary>
/// 尝试读取缓存值。
/// </summary>
/// <param name="value">若已缓存则为输出值,否则为默认值</param>
/// <param name="argument">上下文参数</param>
/// <returns>若已缓存则为 <c>true</c>,否则为 <c>false</c></returns>
public bool TryRead(
[NotNullWhen(true)] out TValue? value,
object? argument = null)
{
bool result;
if (argument is not null) result = _cacheWithContext.TryGetValue(argument, out value);
else
{
if (_hasCachedValue)
{
value = _cachedValue!;
result = true;
}
else
{
value = default;
result = false;
}
}
return result;
}
/// <summary>
/// 写入缓存值。
/// </summary>
/// <param name="value">输入值</param>
/// <param name="argument">上下文参数</param>
public void Write(TValue value, object? argument = null)
{
if (argument is not null)
{
_cacheWithContext[argument] = value;
return;
}
_hasCachedValue = true;
_cachedValue = value;
}
/// <summary>
/// 清除缓存值。<br/>
/// 若缓存存在则清除并返回 <c>true</c>,否则返回 <c>false</c>。
/// </summary>
/// <param name="argument">上下文参数</param>
public bool Invalidate(object? argument)
{
if (argument is not null) return _cacheWithContext.TryRemove(argument, out _);
if (!_hasCachedValue) return false;
_cachedValue = default;
_hasCachedValue = false;
return true;
}
public void InvalidateAll()
{
_cacheWithContext.Clear();
_cachedValue = default;
_hasCachedValue = false;
}
}
@@ -0,0 +1,40 @@
using System.Diagnostics.CodeAnalysis;
namespace PCL.Core.App.Configuration;
public interface IConfigProvider
{
/// <summary>
/// 获取一个值。
/// </summary>
/// <param name="key">键</param>
/// <param name="value">返回值,若不存在则为该类型默认值</param>
/// <param name="argument">上下文参数</param>
/// <typeparam name="T">值的类型</typeparam>
/// <returns>值是否存在,若存在则为 <c>true</c></returns>
public bool GetValue<T>(string key, [NotNullWhen(true)] out T? value, object? argument = null);
/// <summary>
/// 设置一个值。
/// </summary>
/// <param name="key">键</param>
/// <param name="value">新值</param>
/// <param name="argument">上下文参数</param>
/// <typeparam name="T">值的类型</typeparam>
public void SetValue<T>(string key, T value, object? argument = null);
/// <summary>
/// 删除一个值。
/// </summary>
/// <param name="key">键</param>
/// <param name="argument">上下文参数</param>
public void Delete(string key, object? argument = null);
/// <summary>
/// 判断一个值是否存在。
/// </summary>
/// <param name="key">键</param>
/// <param name="argument">上下文参数</param>
/// <returns>值是否存在,若存在则为 <c>true</c></returns>
public bool Exists(string key, object? argument = null);
}
@@ -0,0 +1,30 @@
using System.Collections.Generic;
namespace PCL.Core.App.Configuration;
/// <summary>
/// 配置作用域。
/// </summary>
public interface IConfigScope
{
/// <summary>
/// 检查指定的多个配置项是否在该作用域中。
/// </summary>
/// <param name="keys">配置键</param>
/// <returns>所有存在于该作用域中的键的集合</returns>
public IEnumerable<string> CheckScope(IReadOnlySet<string> keys);
/// <summary>
/// 重置作用域,将使作用域中的所有值回到默认值状态。
/// </summary>
/// <param name="argument">上下文参数</param>
/// <returns>是否成功重置作用域,若成功则为 <c>true</c></returns>
public bool Reset(object? argument = null);
/// <summary>
/// 检查作用域是否为默认。
/// </summary>
/// <param name="argument">上下文参数</param>
/// <returns>若作用域中的所有值均为默认值,则为 <c>true</c>,否则为 <c>false</c></returns>
public bool IsDefault(object? argument = null);
}
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using PCL.Core.Utils.Exts;
namespace PCL.Core.App.Configuration.Storage;
/// <summary>
/// 提供 LTCat-style ini 格式的键值文件读写。
/// </summary>
public class CatIniFileProvider : CommonFileProvider, IEnumerableKeyProvider
{
private readonly Dictionary<string, string> _dict = [];
public CatIniFileProvider(string path) : base(path)
{
if (!File.Exists(path)) return;
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var reader = new StreamReader(stream, Encoding.UTF8);
while (reader.ReadLine() is { } line)
{
if (line.IsNullOrWhiteSpace()) continue;
var split = line.Split(':', 2);
_dict[split[0]] = split[1];
}
}
public IEnumerable<string> Keys => _dict.Keys;
public override T Get<T>(string key)
{
if (!_dict.TryGetValue(key, out var value)) throw new KeyNotFoundException($"Not found: '{key}");
return value.Convert<T>() ?? throw new NullReferenceException();
}
public override void Set<T>(string key, T value)
=> _dict[key] = value.ConvertToString() ?? throw new NullReferenceException();
public override bool Exists(string key) => _dict.ContainsKey(key);
public override void Remove(string key) => _dict.Remove(key);
protected override void WriteToStream(Stream stream)
{
var writer = new StreamWriter(stream, Encoding.UTF8);
foreach (var (key, value) in _dict)
{
var keyStr = key.ReplaceLineBreak();
var valueStr = value.ReplaceLineBreak();
writer.WriteLine($"{keyStr}:{valueStr}");
}
writer.Flush();
}
}
@@ -0,0 +1,31 @@
using System.IO;
using PCL.Core.Utils;
namespace PCL.Core.App.Configuration.Storage;
public abstract class CommonFileProvider(string path) : IKeyValueFileProvider
{
public string FilePath { get; set; } = path;
public abstract T Get<T>(string key);
public abstract void Set<T>(string key, T value);
public abstract bool Exists(string key);
public abstract void Remove(string key);
protected abstract void WriteToStream(Stream stream);
public void Sync()
{
if (!File.Exists(FilePath)) Directory.CreateDirectory(Basics.GetParentPath(FilePath)!);
var tmpFile = $"{FilePath}.tmp{RandomUtils.NextInt(1, 99999):00000}";
var bakFile = $"{FilePath}.bak";
using (var stream = new FileStream(tmpFile, FileMode.Create, FileAccess.Write, FileShare.Read))
{
WriteToStream(stream);
stream.Flush(true);
}
if (File.Exists(FilePath)) File.Replace(tmpFile, FilePath, bakFile);
else File.Move(tmpFile, FilePath);
}
}
@@ -0,0 +1,12 @@
using System;
namespace PCL.Core.App.Configuration.Storage;
public class ConfigFileInitException(string path, string message, Exception? inner = null)
: Exception(message, inner)
{
/// <summary>
/// Relative file path.
/// </summary>
public string Path { get; } = path;
}
@@ -0,0 +1,146 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.Encodings.Web;
using System.Text.Json;
using PCL.Core.App.IoC;
using PCL.Core.Logging;
using PCL.Core.Utils.Diagnostics;
using PCL.Core.Utils;
namespace PCL.Core.App.Configuration.Storage;
public enum StorageAction
{
Get,
Exists,
Set,
Delete
}
/// <summary>
/// 存取仓库模型实现与底层抽象。
/// </summary>
public abstract class ConfigStorage : IConfigProvider
{
protected abstract bool OnAccess<TKey, TValue>(
StorageAction action,
ref TKey key,
[NotNullWhen(true)] ref TValue value,
object? argument);
protected virtual void OnStop() { }
/// <summary>
/// 停止存取工作,保存并释放资源。
/// </summary>
public void Stop() => OnStop();
#if DEBUG
private static readonly bool _EnableTrace = Basics.CommandLineArguments.Contains("--trace-traffic");
#endif
/// <summary>
/// 执行存取操作。
/// </summary>
/// <param name="action">操作类型</param>
/// <param name="key">键</param>
/// <param name="value">值,若无值则为该类型默认值</param>
/// <param name="argument">上下文参数</param>
/// <typeparam name="TKey">键的类型</typeparam>
/// <typeparam name="TValue">值的类型</typeparam>
/// <returns>是否有输出值</returns>
public bool Access<TKey, TValue>(
StorageAction action,
ref TKey key,
[NotNullWhen(true)] ref TValue value,
object? argument)
{
const string logModule = "Config";
var hasOutput = false;
try
{
hasOutput = OnAccess(action, ref key, ref value, argument);
}
catch (Exception ex)
{
var msg = $"Config Storage Error Report\n" +
$"A exception was thrown while processing an access.\n\n" +
$"[Diagnostics Info]\n{_GenerateDiagnosticsInfo(action, key, value, hasOutput, argument, true)}\n\n" +
$"[Exception Details]\n{ex}";
LogWrapper.Fatal(logModule, msg);
Lifecycle.ForceShutdown(-2);
}
#if DEBUG
if (_EnableTrace)
{
LogWrapper.Trace(logModule, _GenerateDiagnosticsInfo(action, key, value, hasOutput, argument));
}
#endif
return hasOutput;
}
private static readonly JsonSerializerOptions _SerializerOptions = new(JsonCompat.SerializerOptions)
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
private string _GenerateDiagnosticsInfo<TKey, TValue>(
StorageAction accessAction,
TKey? accessKey,
TValue? accessValue,
bool accessHasValue,
object? accessContext,
bool appendCallStack = false)
{
#if TRACE
const bool needFileInfo = true;
#else
const bool needFileInfo = false;
#endif
var context = JsonSerializer.Serialize(accessContext, _SerializerOptions);
var key = JsonSerializer.Serialize(accessKey, _SerializerOptions);
var value = JsonSerializer.Serialize(accessValue, _SerializerOptions);
var caller = appendCallStack
? "Stack:\n|=> " + string.Join("\n|=> ", StackHelper.GetStack(includeParameters: true, needFileInfo: needFileInfo).Skip(1))
: "Caller: " + StackHelper.GetDirectCallerName(includeParameters: true, skipAppFrames: 2);
var msg = $"Storage Access: {accessAction} {ToString()}\n" +
$"|- Context: {(accessContext is null ? "" : "(" + accessContext.GetType().Name + ") ")}{context}\n" +
$"|- Key: ({typeof(TKey).Name}) {key}\n" +
$"|- Value: ({typeof(TValue).Name}) {(accessHasValue ? value : "undefined")}\n" +
$"|- {caller}";
return msg;
}
public bool GetValue<T>(string key, [NotNullWhen(true)] out T? value, object? argument = null)
{
var keyRef = key;
T? valueRef = default;
var hasValue = Access(StorageAction.Get, ref keyRef, ref valueRef, argument);
value = valueRef;
return hasValue;
}
public void SetValue<T>(string key, T value, object? argument = null)
{
var keyRef = key;
var valueRef = value;
Access(StorageAction.Set, ref keyRef, ref valueRef, argument);
}
public void Delete(string key, object? argument = null)
{
var keyRef = key;
object? valueRef = null;
Access(StorageAction.Delete, ref keyRef, ref valueRef, argument);
}
public bool Exists(string key, object? argument = null)
{
var keyRef = key;
var resultRef = false;
return Access(StorageAction.Exists, ref keyRef, ref resultRef, argument) && resultRef;
}
public override string ToString() => $"{GetType().Name}@{GetHashCode()}";
}
@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace PCL.Core.App.Configuration.Storage;
public class DynamicCacheConfigStorage : ConfigStorage
{
private readonly Dictionary<object, ConfigStorage> _cache = [];
private ConfigStorage? _nullContextCache;
/// <summary>
/// 存取仓库工厂。在没有匹配的上下文实例时将被调用,以创建新的上下文实例。
/// </summary>
public required Func<object?, ConfigStorage> StorageFactory { get; init; }
protected override bool OnAccess<TKey, TValue>(StorageAction action, ref TKey key, [NotNullWhen(true)] ref TValue value, object? context)
{
ConfigStorage? storage;
if (context is null) storage = _nullContextCache;
else _cache.TryGetValue(context, out storage);
if (storage is null)
{
try
{
storage = StorageFactory(context);
if (context is null) _nullContextCache = storage;
else _cache[context] = storage;
}
catch (Exception ex)
{
throw new Exception("Failed to invoke storage factory", ex);
}
}
return storage.Access(action, ref key, ref value, context);
}
protected override void OnStop()
{
foreach (var item in _cache.Values) item.Stop();
_cache.Clear();
}
public bool InvalidateCache(object context)
{
var result = _cache.TryGetValue(context, out var center);
if (result)
{
center?.Stop();
_cache.Remove(context);
}
return result;
}
}
@@ -0,0 +1,64 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Serialization;
using PCL.Core.Logging;
using PCL.Core.Utils.Secret;
using PCL.Core.Utils;
namespace PCL.Core.App.Configuration.Storage;
public class EncryptedFileConfigStorage(ConfigStorage source) : ConfigStorage
{
public ConfigStorage Source { get; } = source;
private static readonly JsonSerializerOptions _SerializerOptions = new(JsonCompat.SerializerOptions)
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
AllowOutOfOrderMetadataProperties = true,
};
protected override bool OnAccess<TKey, TValue>(StorageAction action, ref TKey key, [NotNullWhen(true)] ref TValue value, object? argument)
{
try
{
switch (action)
{
case StorageAction.Set:
{
// 序列化
var type = typeof(TValue);
string strValue;
if (type == typeof(string)) strValue = value?.ToString() ?? string.Empty;
else strValue = JsonSerializer.Serialize(value, _SerializerOptions);
// 加密
strValue = EncryptHelper.SecretEncrypt(strValue);
return Source.Access(StorageAction.Set, ref key, ref strValue, argument);
}
case StorageAction.Get:
{
// 获取加密值
string? raw = null;
var hasOutput = Source.Access(StorageAction.Get, ref key, ref raw, argument);
if (!hasOutput) return false;
// 解密
var decrypted = EncryptHelper.SecretDecrypt(raw);
// 反序列化
var type = typeof(TValue);
if (type == typeof(bool)) Unsafe.As<TValue, bool>(ref value) = decrypted.ToLowerInvariant() is "true" or "1";
else if (type == typeof(string)) Unsafe.As<TValue, string>(ref value) = decrypted;
else value = JsonSerializer.Deserialize<TValue>(decrypted, _SerializerOptions) ?? throw new NullReferenceException("Decryption produced a null reference");
return hasOutput;
}
default: return Source.Access(action, ref key, ref value, argument);
}
}
catch (Exception ex)
{
LogWrapper.Error(ex, "Config", "无法处理加解密");
return false;
}
}
}
@@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using PCL.Core.App.Localization;
using PCL.Core.Logging;
using PCL.Core.UI;
namespace PCL.Core.App.Configuration.Storage;
/// <summary>
/// 文件存取仓库。
/// </summary>
public class FileConfigStorage : ConfigStorage
{
/// <summary>
/// 键值文件实例。
/// </summary>
public IKeyValueFileProvider File { get; }
private readonly Channel<(string, Action)> _writeActionChannel;
private readonly CancellationTokenSource _writeActionCts;
private readonly ManualResetEventSlim _writeStopEvent = new(true);
public FileConfigStorage(IKeyValueFileProvider file)
{
File = file;
_writeActionChannel = Channel.CreateUnbounded<(string, Action)>();
_writeActionCts = new CancellationTokenSource();
Task.Run(async () =>
{
_writeStopEvent.Reset();
const long syncInterval = 10000; // ms
var lastSyncTick = 0L;
var cancelToken = _writeActionCts.Token;
var writeActionMap = new Dictionary<string, Action>();
var reader = _writeActionChannel.Reader;
try
{
while (!cancelToken.IsCancellationRequested)
{
// 读入并合并暂存操作
var (key, action) = await reader.ReadAsync(cancelToken);
writeActionMap[key] = action;
if (Environment.TickCount64 - lastSyncTick < syncInterval || cancelToken.IsCancellationRequested) continue;
// 同步文件
Sync();
lastSyncTick = Environment.TickCount64;
writeActionMap.Clear();
}
}
catch (OperationCanceledException) { /* ignoring*/ }
finally
{
// 结束时执行一次同步
Sync();
}
_writeStopEvent.Set();
return;
void Sync()
{
try
{
LogWrapper.Trace("Config", $"正在保存 {File.FilePath}");
foreach (var action in writeActionMap.Values) action();
File.Sync();
}
catch (Exception ex)
{
LogWrapper.Error(ex, "Config", "配置文件保存失败");
var summary = Lang.Text("Config.Error.SaveFailed.Message", File.FilePath);
var message = ExceptionDetails.Compose(summary, ex);
MsgBoxWrapper.Show(
message,
Lang.Text("Config.Error.SaveFailed.Title"),
MsgBoxTheme.Error);
}
}
});
}
protected override void OnStop()
{
_writeActionCts.Cancel();
_writeStopEvent.Wait();
_writeStopEvent.Dispose();
}
protected override bool OnAccess<TKey, TValue>(
StorageAction action,
ref TKey key,
[NotNullWhen(true)] ref TValue value,
object? argument)
{
if (key is not string strKey) throw new NotSupportedException($"Key '{key}' is not supported");
#pragma warning disable CS8762 // Parameter must have a non-null value when exiting in some condition.
switch (action)
{
case StorageAction.Get:
if (!File.Exists(strKey)) return false;
try
{
value = File.Get<TValue>(strKey);
}
catch (Exception ex) when (ex is JsonException
or InvalidCastException
or FormatException
or OverflowException
or ArgumentException
or KeyNotFoundException
or InvalidDataException)
{
LogWrapper.Warn(ex, "Config", $"配置项 {strKey} 读取失败(可能已损坏),重置为默认值");
if (!_writeActionChannel.Writer.TryWrite((strKey, () => File.Remove(strKey))))
{
LogWrapper.Warn("Config", $"配置项 {strKey} 清理任务入队失败,改为同步删除");
try
{
File.Remove(strKey);
File.Sync();
}
catch (Exception cleanupEx)
{
LogWrapper.Error(cleanupEx, "Config", $"配置项 {strKey} 同步删除失败,可能需人工处理");
}
}
return false;
}
return true;
case StorageAction.Exists:
// 由于 Exists 的 value 类型一定是 bool,此处可 unsafe 直接赋值
if (typeof(TValue) == typeof(bool)) Unsafe.As<TValue, bool>(ref value) = File.Exists(strKey);
else throw new InvalidOperationException($"Storage action '{StorageAction.Exists}' must have a boolean value");
return true;
case StorageAction.Set:
var localValue = value;
_writeActionChannel.Writer.TryWrite((strKey, () => File.Set(strKey, localValue)));
return false;
case StorageAction.Delete:
_writeActionChannel.Writer.TryWrite((strKey, () => File.Remove(strKey)));
return false;
default: throw new InvalidOperationException($"Invalid storage action: {action}");
}
#pragma warning restore CS8762 // Parameter must have a non-null value when exiting in some condition.
}
public override string ToString() => $"{base.ToString()} ({File.FilePath})";
}
@@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace PCL.Core.App.Configuration.Storage;
public interface IEnumerableKeyProvider
{
/// <summary>
/// 获取文件包含的所有键。通常是一个耗时操作,慎用。
/// </summary>
public IEnumerable<string> Keys { get; }
}
@@ -0,0 +1,37 @@
namespace PCL.Core.App.Configuration.Storage;
/// <summary>
/// 键值文件模型。
/// </summary>
public interface IKeyValueFileProvider
{
/// <summary>
/// 文件路径。
/// </summary>
public string FilePath { get; }
/// <summary>
/// 获取一个值。
/// </summary>
public T Get<T>(string key);
/// <summary>
/// 设置一个值。
/// </summary>
public void Set<T>(string key, T value);
/// <summary>
/// 判断一个值是否存在。
/// </summary>
public bool Exists(string key);
/// <summary>
/// 移除一个值。
/// </summary>
public void Remove(string key);
/// <summary>
/// 写入文件。
/// </summary>
public void Sync();
}
@@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using PCL.Core.Utils;
namespace PCL.Core.App.Configuration.Storage;
/// <summary>
/// 提供 JSON 格式的键值文件读写。
/// </summary>
public class JsonFileProvider : CommonFileProvider, IEnumerableKeyProvider
{
private readonly JsonObject _rootElement;
private static readonly JsonDocumentOptions _DocumentOptions = JsonCompat.DocumentOptions;
private static readonly JsonSerializerOptions _SerializerOptions = new(JsonCompat.SerializerOptions)
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
AllowOutOfOrderMetadataProperties = true,
};
private static readonly JsonWriterOptions _WriterOptions = new()
{
Indented = true
};
public JsonFileProvider(string path) : base(path)
{
try
{
if (File.Exists(path))
{
using var stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var parseResult = JsonNode.Parse(stream, JsonCompat.NodeOptions, _DocumentOptions);
if (parseResult is not JsonObject root)
throw new ConfigFileInitException(path,
$"Invalid root element type: {parseResult?.GetValueKind().ToString() ?? "Empty"}");
_rootElement = root;
}
else
{
using var stream = new FileStream(FilePath, FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite);
_rootElement = new JsonObject();
JsonSerializer.Serialize(stream, _rootElement, _SerializerOptions);
}
}
catch (Exception ex)
{
if (ex is ConfigFileInitException) throw;
throw new ConfigFileInitException(path, "Failed to read JSON file", ex);
}
}
public override T Get<T>(string key)
{
var result = _rootElement[key];
if (result is null) throw new KeyNotFoundException($"Not found: '{key}'");
try
{
var r = result.Deserialize<T>(_SerializerOptions);
return r ?? throw GetNullException();
}
catch (JsonException)
{
T fallback;
var type = typeof(T);
if (type == typeof(string)) fallback = (T)(object)result.ToString();
else
{
var jsonStr = result.Deserialize<string>(_SerializerOptions)!;
if (type == typeof(bool)) fallback = (T)(object)(jsonStr.ToLowerInvariant() is "true" or "1");
else fallback = JsonSerializer.Deserialize<T>(jsonStr, _SerializerOptions) ?? throw GetNullException();
}
Set(key, fallback);
return fallback;
}
Exception GetNullException() => new InvalidDataException($"Deserialized value is null: '{key}'");
}
public override void Set<T>(string key, T value)
{
_rootElement[key] = JsonSerializer.SerializeToNode(value, _SerializerOptions);
}
public override bool Exists(string key)
{
return _rootElement.ContainsKey(key);
}
public override void Remove(string key)
{
_rootElement.Remove(key);
}
protected override void WriteToStream(Stream stream)
{
var writer = new Utf8JsonWriter(stream, _WriterOptions);
_rootElement.WriteTo(writer, _SerializerOptions);
writer.Flush();
}
public IEnumerable<string> Keys => _rootElement.Select(pair => pair.Key);
}
@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using YamlDotNet.Serialization;
using PCL.Core.Utils;
namespace PCL.Core.App.Configuration.Storage;
// Partly generated by gpt-5-mini (20250903)
public static class JsonToYamlConverter
{
/// <summary>
/// 从 jsonInput 读取 JSON,转换为 YAML 写入 yamlOutput。
/// </summary>
/// <param name="jsonInput">可读的 JSON 输入流</param>
/// <param name="yamlOutput">可写的 YAML 输出流</param>
/// <param name="leaveOpen">是否在返回时保留输出流打开</param>
public static void Convert(Stream jsonInput, Stream yamlOutput, bool leaveOpen = false)
{
ArgumentNullException.ThrowIfNull(jsonInput);
ArgumentNullException.ThrowIfNull(yamlOutput);
if (!jsonInput.CanRead) throw new ArgumentException("must be readable", nameof(jsonInput));
if (!yamlOutput.CanWrite) throw new ArgumentException("must be writable", nameof(yamlOutput));
using var doc = JsonDocument.Parse(jsonInput, JsonCompat.DocumentOptions);
var obj = _ConvertElement(doc.RootElement);
var serializer = new SerializerBuilder().Build();
using var writer = new StreamWriter(yamlOutput, new UTF8Encoding(false), 8192, leaveOpen);
serializer.Serialize(writer, obj);
writer.Flush();
}
/// <summary>
/// 异步从 jsonInput 读取 JSON,转换为 YAML 写入 yamlOutput。
/// </summary>
/// <param name="jsonInput">可读的 JSON 输入流</param>
/// <param name="yamlOutput">可写的 YAML 输出流</param>
/// <param name="leaveOpen">是否在返回时保留输出流打开</param>
public static async Task ConvertAsync(Stream jsonInput, Stream yamlOutput, bool leaveOpen = false)
{
ArgumentNullException.ThrowIfNull(jsonInput);
ArgumentNullException.ThrowIfNull(yamlOutput);
if (!jsonInput.CanRead) throw new ArgumentException("jsonInput must be readable", nameof(jsonInput));
if (!yamlOutput.CanWrite) throw new ArgumentException("yamlOutput must be writable", nameof(yamlOutput));
using var doc = await JsonDocument.ParseAsync(jsonInput, JsonCompat.DocumentOptions).ConfigureAwait(false);
var obj = _ConvertElement(doc.RootElement);
var serializer = new SerializerBuilder().Build();
await using var streamWriter = new StreamWriter(yamlOutput, new UTF8Encoding(false), 8192, leaveOpen);
serializer.Serialize(streamWriter, obj);
await streamWriter.FlushAsync().ConfigureAwait(false);
}
private static object? _ConvertElement(JsonElement element)
{
switch (element.ValueKind)
{
case JsonValueKind.Object:
{
var dict = new Dictionary<string, object?>(StringComparer.Ordinal);
foreach (var prop in element.EnumerateObject())
{
dict[prop.Name] = _ConvertElement(prop.Value);
}
return dict;
}
case JsonValueKind.Array:
{
return element.EnumerateArray().Select(_ConvertElement).ToList();
}
case JsonValueKind.String: return element.GetString();
case JsonValueKind.Number:
{
// 尽量保留数值类型:先尝试 Int64,再尝试 decimal(避免浮点精度丢失),最后尝试 double
if (element.TryGetInt64(out var l)) return l;
var raw = element.GetRawText();
if (decimal.TryParse(raw, NumberStyles.Any, CultureInfo.InvariantCulture, out var dec)) return dec;
if (element.TryGetDouble(out var d)) return d;
// 兜底:返回原始文本
return raw;
}
case JsonValueKind.True: return true;
case JsonValueKind.False: return false;
case JsonValueKind.Null:
case JsonValueKind.Undefined:
default:
return null;
}
}
}
@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using PCL.Core.Logging;
using YamlDotNet.RepresentationModel;
using YamlDotNet.Serialization;
namespace PCL.Core.App.Configuration.Storage;
/// <summary>
/// 提供 YAML 格式的键值文件读写。当提供的文件找不到时,将尝试读取其它同名文件并将其转换到 YAML。
/// </summary>
public class YamlFileProvider : CommonFileProvider, IEnumerableKeyProvider
{
private readonly YamlMappingNode _rootNode;
private static readonly IDeserializer _Deserializer = new DeserializerBuilder()
.IgnoreUnmatchedProperties().WithEnforceRequiredMembers().Build();
private static readonly ISerializer _Serializer = new SerializerBuilder()
.DisableAliases().Build();
private static YamlMappingNode? _LoadFile(string path)
{
if (!File.Exists(path)) return null;
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var reader = new StreamReader(stream, Encoding.UTF8);
try
{
var yaml = new YamlStream();
yaml.Load(reader);
if (yaml.Documents.Count == 0) return [];
var rootNode = yaml.Documents[0].RootNode;
return rootNode as YamlMappingNode ?? throw new ConfigFileInitException(path, $"Invalid root node type: {rootNode.NodeType}");
}
catch (Exception ex)
{
if (ex is ConfigFileInitException) throw;
throw new ConfigFileInitException(path, "Failed to load YAML content", ex);
}
}
public YamlFileProvider(string path) : base(path)
{
var rootNode = _LoadFile(path);
if (rootNode is not null)
{
_rootNode = rootNode;
return;
}
try // 尝试从 JSON 和 LTCat-style ini 转换
{
var jsonPath = Path.Combine(Basics.GetParentPath(path)!, Path.GetFileNameWithoutExtension(path) + ".json");
if (File.Exists(jsonPath))
{
using var jsonStream = new FileStream(jsonPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var yamlStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
JsonToYamlConverter.Convert(jsonStream, yamlStream); // yamlStream 会被自动关闭
_rootNode = _LoadFile(path)!;
return;
}
}
catch (Exception ex)
{
LogWrapper.Warn(ex, "转换失败,已忽略");
}
_rootNode = [];
}
public override T Get<T>(string key)
{
var result = _rootNode.Children[key];
var parser = result.ConvertToEventStream().GetParser();
try
{
return _Deserializer.Deserialize<T>(parser);
}
catch (Exception)
{
var type = typeof(T);
var graphStr = result.ToString();
var fallback = (type == typeof(bool))
? (T)(object)(graphStr.ToLowerInvariant() is "true" or "1")
: (T)(object)graphStr;
Set(key, fallback);
return fallback;
}
}
public override void Set<T>(string key, T value)
{
var emitter = new YamlNodeEmitter();
_Serializer.Serialize(emitter, value);
_rootNode.Children[key] = emitter.SingleRootNode;
}
public override bool Exists(string key)
{
return _rootNode.Children.ContainsKey(key);
}
public override void Remove(string key)
{
_rootNode.Children.Remove(key);
}
protected override void WriteToStream(Stream stream)
{
var writer = new StreamWriter(stream, Encoding.UTF8);
_Serializer.Serialize(writer, _rootNode);
writer.Flush();
}
public IEnumerable<string> Keys => _rootNode.Select(pair => pair.Key.ToString());
}
@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.RepresentationModel;
namespace PCL.Core.App.Configuration.Storage;
// 修改自: https://dotnetfiddle.net/jaG1i1
// 来源: https://stackoverflow.com/a/40727087
// 原作者: Antoine Aubry (是 YamlDotNet 的作者, 真不懂都造出来了为什么不直接把它写到库里)
public static class YamlNodeConverter
{
public class EventStreamParserAdapter(IEnumerable<ParsingEvent> events) : IParser
{
private readonly IEnumerator<ParsingEvent> _enumerator = events.GetEnumerator();
public ParsingEvent Current => _enumerator.Current;
public bool MoveNext() => _enumerator.MoveNext();
}
public static IParser GetParser(this IEnumerable<ParsingEvent> eventStream)
{
return new EventStreamParserAdapter(eventStream);
}
public static IEnumerable<ParsingEvent> ConvertToEventStream(this YamlStream stream)
{
yield return new StreamStart();
foreach (var document in stream.Documents)
{
foreach (var evt in document.ConvertToEventStream())
{
yield return evt;
}
}
yield return new StreamEnd();
}
public static IEnumerable<ParsingEvent> ConvertToEventStream(this YamlDocument document)
{
yield return new DocumentStart();
foreach (var evt in document.RootNode.ConvertToEventStream())
{
yield return evt;
}
yield return new DocumentEnd(false);
}
public static IEnumerable<ParsingEvent> ConvertToEventStream(this YamlNode node)
{
if (node is YamlScalarNode scalar)
{
return _ConvertToEventStream(scalar);
}
if (node is YamlSequenceNode sequence)
{
return _ConvertToEventStream(sequence);
}
if (node is YamlMappingNode mapping)
{
return _ConvertToEventStream(mapping);
}
throw new NotSupportedException($"Unsupported node type: {node.GetType().Name}");
}
private static IEnumerable<ParsingEvent> _ConvertToEventStream(YamlScalarNode scalar)
{
yield return new Scalar(scalar.Anchor, scalar.Tag, scalar.Value!, scalar.Style, false, false);
}
private static IEnumerable<ParsingEvent> _ConvertToEventStream(YamlSequenceNode sequence)
{
yield return new SequenceStart(sequence.Anchor, sequence.Tag, false, sequence.Style);
foreach (var node in sequence.Children)
{
foreach (var evt in node.ConvertToEventStream())
{
yield return evt;
}
}
yield return new SequenceEnd();
}
private static IEnumerable<ParsingEvent> _ConvertToEventStream(YamlMappingNode mapping)
{
yield return new MappingStart(mapping.Anchor, mapping.Tag, false, mapping.Style);
foreach (var pair in mapping.Children)
{
foreach (var evt in pair.Key.ConvertToEventStream())
{
yield return evt;
}
foreach (var evt in pair.Value.ConvertToEventStream())
{
yield return evt;
}
}
yield return new MappingEnd();
}
}
@@ -0,0 +1,197 @@
using System;
using System.Collections.Generic;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.RepresentationModel;
namespace PCL.Core.App.Configuration.Storage;
// Partly generated by gpt-5 (20250903)
public sealed class YamlNodeEmitter : IEmitter
{
private readonly List<YamlDocument> _documents = [];
private readonly Stack<Container> _stack = new();
private readonly Dictionary<string, YamlNode> _anchors = new(StringComparer.Ordinal);
private bool _inStream;
public IReadOnlyList<YamlDocument> Documents => _documents;
public YamlNode SingleRootNode => _documents.Count switch
{
0 => throw new InvalidOperationException("尚未产生任何文档。"),
1 => _documents[0].RootNode,
_ => throw new InvalidOperationException("存在多个文档,请使用 Documents 访问。")
};
public void Reset()
{
_documents.Clear();
_stack.Clear();
_anchors.Clear();
_inStream = false;
}
public void Emit(ParsingEvent @event)
{
switch (@event)
{
case StreamStart:
Reset();
_inStream = true;
break;
case StreamEnd:
_inStream = false;
if (_stack.Count != 0)
throw new InvalidOperationException("事件结束时堆栈未清空,事件序列不平衡。");
break;
case DocumentStart:
_RequireStream();
_stack.Push(Container.Document());
break;
case DocumentEnd:
{
_RequireStream();
if (_stack.Count == 0 || _stack.Peek().Kind != ContainerKind.Document)
throw new InvalidOperationException("DocumentEnd 前缺少对应的 DocumentStart。");
var doc = _stack.Pop();
if (doc.Node is null)
throw new InvalidOperationException("空文档:未设置根节点。");
_documents.Add(new YamlDocument(doc.Node));
}
break;
case MappingStart mapStart:
{
var map = new YamlMappingNode();
_ApplyAnchor(mapStart, map);
_AttachToParent(map);
_stack.Push(Container.Mapping(map));
}
break;
case MappingEnd:
{
if (_stack.Count == 0 || _stack.Peek().Kind != ContainerKind.Mapping)
throw new InvalidOperationException("MappingEnd 前缺少对应的 MappingStart。");
var finished = _stack.Pop();
// no-op: 已在 Start 时挂接到父级
if (finished.PendingKey is not null)
throw new InvalidOperationException("映射以键结尾,缺少对应的值。");
}
break;
case SequenceStart seqStart:
{
var seq = new YamlSequenceNode();
_ApplyAnchor(seqStart, seq);
_AttachToParent(seq);
_stack.Push(Container.Sequence(seq));
}
break;
case SequenceEnd:
{
if (_stack.Count == 0 || _stack.Peek().Kind != ContainerKind.Sequence)
throw new InvalidOperationException("SequenceEnd 前缺少对应的 SequenceStart。");
_stack.Pop(); // 已在 Start 时挂接到父级
}
break;
case Scalar scalar:
{
var node = new YamlScalarNode(scalar.Value);
_ApplyAnchor(scalar, node);
_AttachToParent(node);
}
break;
case AnchorAlias alias:
{
var anchorName = alias.Value.Value; // AnchorName.Value -> string
if (!_anchors.TryGetValue(anchorName, out var target))
throw new InvalidOperationException($"未找到锚点 '{anchorName}' 的定义。");
_AttachToParent(target);
}
break;
}
}
private void _RequireStream()
{
if (!_inStream)
throw new InvalidOperationException("必须在 StreamStart 与 StreamEnd 之间接收事件。");
}
private void _ApplyAnchor(NodeEvent nodeEvent, YamlNode node)
{
// 仅在存在锚点时登记;Tag/Style 等可按需扩展
if (nodeEvent.Anchor.IsEmpty) return;
var name = nodeEvent.Anchor.Value;
node.Anchor = name;
// 最新 YamlDotNet 表示模型允许同名锚点复用同一节点引用;
// 若重复定义同名锚点,认为是非法
if (!_anchors.TryAdd(name, node))
throw new InvalidOperationException($"锚点 '{name}' 被重复定义。");
}
private void _AttachToParent(YamlNode node)
{
if (_stack.Count == 0)
{
throw new InvalidOperationException("缺少 DocumentStart:无法确定根节点所属文档。");
}
var parent = _stack.Peek();
switch (parent.Kind)
{
case ContainerKind.Document:
if (parent.Node is not null)
throw new InvalidOperationException("一个文档只能包含一个根节点。");
parent.Node = node;
_stack.Pop();
_stack.Push(parent); // 写回修改
break;
case ContainerKind.Sequence:
((YamlSequenceNode)parent.Node!).Add(node);
break;
case ContainerKind.Mapping:
if (parent.PendingKey is null)
{
parent.PendingKey = node; // 作为键
}
else
{
((YamlMappingNode)parent.Node!).Add(parent.PendingKey, node);
parent.PendingKey = null;
}
_stack.Pop();
_stack.Push(parent); // 写回修改
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private enum ContainerKind { Document, Mapping, Sequence }
private struct Container
{
public ContainerKind Kind;
public YamlNode? Node;
public YamlNode? PendingKey;
public static Container Document() => new() { Kind = ContainerKind.Document, Node = null, PendingKey = null };
public static Container Mapping(YamlMappingNode map) => new() { Kind = ContainerKind.Mapping, Node = map, PendingKey = null };
public static Container Sequence(YamlSequenceNode seq) => new() { Kind = ContainerKind.Sequence, Node = seq, PendingKey = null };
}
}
@@ -0,0 +1,48 @@
using System;
using LiteDB;
namespace PCL.Core.App.Database;
/// <summary>
/// Base database connention entry.
/// </summary>
public abstract class DatabaseEntry : IDisposable
{
protected readonly LiteDatabase Db;
private bool _disposed = false;
/// <exception cref="ArgumentNullException">Throw if connection path is invalid.</exception>
protected DatabaseEntry(string connPath)
{
var db = DatabaseService.GetConnection(connPath);
Db = db ?? throw new ArgumentNullException(nameof(db), "Database variable can not be null.");
}
/// <exception cref="ArgumentNullException">Throw if connection path is invalid.</exception>
protected DatabaseEntry(LiteDatabase database)
{
Db = database ?? throw new ArgumentNullException(nameof(database), "Database variable can not be null.");
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
// NOTE: do not dispose Db, because it is managed by DatabaseService
}
_disposed = true;
}
}
@@ -0,0 +1,39 @@
using System;
using System.Collections.Concurrent;
using LiteDB;
using PCL.Core.App.IoC;
namespace PCL.Core.App.Database;
[LifecycleService(LifecycleState.Loading)]
public class DatabaseService() : GeneralService("database", "数据库管理")
{
private static readonly ConcurrentDictionary<string, LiteDatabase> _Instances = new();
/// <inheritdoc />
public override void Stop()
{
foreach (var instance in _Instances.Values)
{
instance.Dispose();
}
_Instances.Clear();
}
/// <summary>
/// Get the database connenction from specified connection path.<br/>
/// If not exists, a new connection will be created and cached.
/// </summary>
/// <returns>Getted connenction instance.</returns>
/// <exception cref="ArgumentException">Throw if connection path is invalid.</exception>
public static LiteDatabase GetConnection(string connectionPath)
{
if (string.IsNullOrWhiteSpace(connectionPath))
{
throw new ArgumentException("Connection string canot be null or whitespace.", nameof(connectionPath));
}
return _Instances.GetOrAdd(connectionPath, cp => new LiteDatabase(cp));
}
}
@@ -0,0 +1,35 @@
# Database Guildline
This is a light database management guildline for PCL.Core. It is not a strict rule, but a recommendation to follow.
## How to create a new database
1. You need create a class that inheritance from `DatabaseEntry`.
2. Then make a ctor that call `base(<connPath>)` with the connection path.
3. Now this class is ready to use. (Yes it is that simple, and will automatically create the database file if not exists)
## DatabaseEntry
The `DatabaseEntry` class is the entrance to use the database.
User must implement a class that inheritance from `DatabaseEntry` to use it.
And the child class should implement there own method to provide some ways to interact with the database.
## A Normal Example
```charp
public class UserDatabase : DatabaseEntry
{
public UserDatabase() : base("example.db")
{
// You can do some initialization here.
// Like create table if not exists,
// or anything else.
}
public List<(int Id, string Name, int Age)> GetAllUsers()
{
// In your method, you shoul provide some way to interact with the database.
}
}
```
Then, you can use it like this:
```charp
var db = new UserDatabase();
var users = db.GetAlUsers();
```
@@ -0,0 +1,65 @@
using System;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using PCL.Core.App.IoC;
namespace PCL.Core.App.Essentials;
[LifecycleService(LifecycleState.BeforeLoading, Priority = int.MinValue)]
[LifecycleScope("application", "应用程序", false)]
public sealed partial class ApplicationService
{
public static Func<Application>? Loading { private get; set; }
[LifecycleStart]
private static void _Start()
{
Context.Debug("正在初始化 WPF 应用程序容器");
var app = Loading!.Invoke();
app.DispatcherUnhandledException += (_, e) => Lifecycle.OnException(e.Exception);
app.Startup += (_, _) => Lifecycle.OnLoading();
Lifecycle.CurrentApplication = app;
Loading = null;
Context.Trace("应用程序容器初始化完毕");
}
[LifecycleStop]
private static void _Stop()
{
var app = Lifecycle.CurrentApplication;
var dispatcher = app.Dispatcher;
if (Lifecycle.IsForceShutdown)
{
Context.Warn("已指定强制关闭,跳过 WPF 标准关闭流程");
return;
}
if (dispatcher is null || dispatcher.HasShutdownFinished) return;
using var exited = new ManualResetEventSlim();
dispatcher.BeginInvoke(DispatcherPriority.Send, () =>
{
app.Exit += Exited;
if (dispatcher.HasShutdownStarted) return;
Context.Debug("发起 WPF 退出流程");
app.Shutdown();
});
try
{
Context.Debug("正在等待应用程序容器退出");
var result = exited.Wait(5000);
if (result) Context.Trace("应用程序容器已退出");
else Context.Warn("应用程序容器退出超时,停止等待");
}
finally
{
dispatcher.BeginInvoke(DispatcherPriority.Send, () => app.Exit -= Exited);
}
return;
void Exited(object? sender, EventArgs e)
{
// ReSharper disable once AccessToDisposedClosure
exited.Set();
}
}
}
@@ -0,0 +1,24 @@
using System;
using System.Windows;
using PCL.Core.App.IoC;
namespace PCL.Core.App.Essentials;
[LifecycleService(LifecycleState.WindowCreating, Priority = int.MaxValue)]
public sealed class MainWindowService : GeneralService
{
public static Func<Window>? Loading { private get; set; }
private static LifecycleContext? _context;
private static LifecycleContext Context => _context!;
private MainWindowService() : base("window", "主窗体", false) { _context = ServiceContext; }
public override void Start()
{
Context.Debug("正在初始化 WPF 窗体");
var window = Loading!.Invoke();
window.Loaded += (_, _) => Lifecycle.OnWindowCreated();
Lifecycle.CurrentApplication.MainWindow = window;
Context.Trace("窗体创建完毕");
}
}
@@ -0,0 +1,340 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Text.Json;
using System.Threading;
using PCL.Core.App.IoC;
using PCL.Core.IO;
using PCL.Core.Logging;
using PCL.Core.Utils.OS;
using PCL.Core.Utils;
namespace PCL.Core.App.Essentials;
public delegate string? PromoteOperationFunction(string? arg);
/// <summary>
/// 标记一个方法,使其能够被提权进程调用,方法签名需符合 <see cref="PromoteOperationFunction"/>。
/// </summary>
/// <param name="name">提权操作名</param>
[DependencyCollector<PromoteOperationFunction>("promote", AttributeTargets.Method)]
[AttributeUsage(AttributeTargets.Method)]
public sealed class PromoteOperationAttribute(string name) : Attribute;
[LifecycleService(LifecycleState.BeforeLoading, Priority = -10)]
[LifecycleScope("promote", "提权服务", false)]
public sealed partial class PromoteService
{
private static Process? _promoteProcess;
private static NamedPipeServerStream? _promotePipeServer;
private static readonly ConcurrentQueue<PromoteOperation> _PendingOperations = [];
private readonly record struct PromoteOperation(string Command, Action<string?>? Callback, bool DetailLog);
/// <summary>
/// 提权进程是否正在运行。
/// </summary>
public static bool IsPromoteProcessRunning => _promoteProcess is not null;
/// <summary>
/// 当前进程是否是提权进程。
/// </summary>
public static bool IsCurrentProcessPromoted { get; private set; }
private static string _GetPromotePipeName(int processId) => $"PCLCE_PM@{processId}";
private static readonly Dictionary<string, PromoteOperationFunction> _OperationFunctions = new();
/// <summary>
/// 添加提权操作,仅在提权进程中有效。
/// </summary>
/// <param name="name">操作名</param>
/// <param name="operation">操作实现,接收参数并返回结果,返回值会被自动压缩为单行</param>
/// <returns>是否添加成功,若在主进程中调用或已存在相同操作名,则为 <c>false</c></returns>
public static bool AddOperationFunction(string name, PromoteOperationFunction operation)
{
return IsCurrentProcessPromoted && _OperationFunctions.TryAdd(name, operation);
}
/// <summary>
/// 添加自动将参数 JSON 反序列化的提权操作,仅在提权进程中有效。
/// </summary>
/// <param name="name">操作名</param>
/// <param name="operation">操作实现,接收反序列化的并返回结果,返回值会被自动压缩为单行</param>
/// <typeparam name="TValue">反序列化的目标类型</typeparam>
/// <returns>是否添加成功,若在主进程中调用或已存在相同操作名,则为 <c>false</c></returns>
public static bool AddJsonOperationFunction<TValue>(string name, Func<TValue?, string?> operation)
{
return AddOperationFunction(name, arg =>
{
if (arg is null) return OperationErrEmpty;
var obj = JsonSerializer.Deserialize<TValue>(arg, JsonCompat.SerializerOptions);
return operation(obj);
});
}
private const string OperationErrNotFound = "ERR_OPERATION_NOT_FOUND";
private const string OperationErrInvalidArgument = "ERR_ILLEGAL_ARGUMENT";
private const string OperationErrExceptionThrown = "ERR_UNHANDLED_EXCEPTION";
private const string OperationErrEmpty = "ERR_EMPTY";
/// <summary>
/// 提权进程接收到操作请求时触发的事件,接收一个字符串作为操作命令并返回一个字符串作为结果。<br/>
/// <b>注意:如果你不知道这是做什么的,请勿覆盖默认实现。</b>请使用 <see cref="AddOperationFunction"/>。
/// </summary>
public static Func<string, string?> Operate {
private get => field ??= command =>
{
var split = command.Split([' '], 2);
_OperationFunctions.TryGetValue(split[0], out var operation);
if (operation is null) return OperationErrNotFound;
try
{
return operation(split.Length > 1 ? split[1] : null) ?? OperationErrEmpty;
}
catch (Exception ex)
{
Context.Warn("操作出错", ex);
return OperationErrExceptionThrown;
}
};
set;
} = null!;
private static string _ShortenString(string str)
{
#if TRACE
const int maxLength = 40;
#else
const int maxLength = 15;
#endif
if (str.Length <= maxLength) return str;
return str[..maxLength] + "...";
}
// 提权进程: 连接管道开始通信
private static void _PerformAsPromoteProcess(string pid)
{
Context.Info("正在连接提权通信管道");
var process = Process.GetProcessById(int.Parse(pid));
// 验证来源
var mainProcessPath = Path.GetFullPath(process.MainModule!.FileName);
if (!string.Equals(mainProcessPath, Basics.ExecutablePath, StringComparison.OrdinalIgnoreCase))
{
Context.Error("来源验证失败,正在退出");
return;
}
// 连接管道
var pipeName = _GetPromotePipeName(process.Id);
var pipe = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut);
pipe.Connect(10000);
Context.Info("已连接,开始通信");
var reader = new StreamReader(pipe);
var writer = new StreamWriter(pipe);
while (true)
{
var command = reader.ReadLine();
if (string.IsNullOrEmpty(command))
{
Context.Info("管道已关闭,正在退出");
break;
}
Context.Debug($"正在执行: {_ShortenString(command)}");
var result = Operate(command) ?? OperationErrEmpty;
Context.Trace($"返回结果: {_ShortenString(result)}");
writer.WriteLine(result.Replace("\r\n", " ").Replace('\n', ' ').Replace('\r', ' '));
writer.Flush();
Context.Trace("返回成功");
}
}
private static readonly AutoResetEvent _ActivateEvent = new(false);
// 主进程: 管道连接回调
private static bool _PromotePipeCallback(StreamReader reader, StreamWriter writer, Process? client)
{
while (IsPromoteProcessRunning)
{
if (!_PendingOperations.TryDequeue(out var operation))
{
_ActivateEvent.WaitOne();
continue;
}
var command = operation.Command.Replace("\r\n", " ").Replace('\n', ' ').Replace('\r', ' ');
var commandLog = operation.DetailLog ? command : _ShortenString(command);
Context.Debug($"正在执行: {commandLog}");
writer.WriteLine(command);
writer.Flush();
var result = reader.ReadLine();
if (result is null)
{
Context.Warn("管道输入流已结束");
break;
}
var resultLog = operation.DetailLog ? result : _ShortenString(result);
Context.Trace($"执行结果: {resultLog}");
if (result == OperationErrEmpty) result = null;
operation.Callback?.Invoke(result);
}
return false;
}
// 主进程: 初始化提权后台服务
private static bool _StartPromoteProcess()
{
// 启动提权进程
_promoteProcess = ProcessInterop.Start(
Basics.ExecutablePath, $"promote {Basics.CurrentProcessId}", true);
if (_promoteProcess is null)
{
Context.Warn("提权进程启动失败");
return false;
}
_promoteProcess.Exited += (_, _) => _promoteProcess = null;
// 启动提权通信管道服务端
_promotePipeServer ??= PipeComm.StartPipeServer(
"Promote", _GetPromotePipeName(Basics.CurrentProcessId), _PromotePipeCallback,
() => _promotePipeServer = null, true, [_promoteProcess.Id]);
return true;
}
/// <summary>
/// 向等待区添加操作。
/// </summary>
/// <param name="command">操作命令</param>
/// <param name="callback">结果返回后的回调</param>
/// <param name="detailLog">指定是否打印详细日志,若为 <c>false</c>,则日志仅保留前 40 或 15 字符(取决于是否为调试构建)</param>
public static void Append(string command, Action<string?>? callback = null, bool detailLog = true)
{
_PendingOperations.Enqueue(new PromoteOperation(command, callback, detailLog));
}
/// <summary>
/// 尝试启动提权进程并开始执行操作。
/// </summary>
/// <returns>是否成功开始执行,若提权进程启动失败则为 <c>false</c></returns>
public static bool Activate()
{
if (!IsPromoteProcessRunning && !_StartPromoteProcess())
{
_PendingOperations.Clear();
return false;
}
_ActivateEvent.Set();
return true;
}
private static readonly Dictionary<string, Process> _RunningProcesses = new();
// name: kill
// arg: process-id [timeout]
// return: kill result (false over timeout)
[PromoteOperation("kill")]
public static string? KillProcess(string? arg)
{
if (arg is null) return OperationErrInvalidArgument;
var split = arg.Split(' ');
if (!_RunningProcesses.TryGetValue(split[0], out var process)) return null;
process.Kill();
if (split.Length > 1)
{
int.TryParse(split[1], out var timeout);
return process.WaitForExit(timeout).ToString();
}
process.WaitForExit();
return true.ToString();
}
// name: start
// arg: path\to\executable[.] ; arguments
// return: process id
[PromoteOperation("start")]
public static string? StartProcess(string? arg)
{
if (arg is null) return OperationErrInvalidArgument;
var split = arg.Split([" ; "], 2, StringSplitOptions.RemoveEmptyEntries);
var createNoWindow = false;
if (split[0].EndsWith('.'))
{
split[0] = split[0][..^1];
createNoWindow = true;
}
var psi = new ProcessStartInfo(split[0]);
if (createNoWindow)
{
psi.CreateNoWindow = true;
psi.UseShellExecute = false;
psi.RedirectStandardInput = true;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
}
if (split.Length > 1) psi.Arguments = split[1];
return _StartProcessWithInfo(psi);
}
// name: start-json
// arg: {...}
// return: process id
private static string? _StartProcessWithInfo(ProcessStartInfo? info)
{
if (info is null) return OperationErrInvalidArgument;
var process = Process.Start(info);
if (process is null) return null;
var id = process.Id.ToString();
process.Exited += (_, _) => _RunningProcesses.Remove(id);
_RunningProcesses[id] = process;
return id;
}
[DependencyInjectionPoint("promote", false)]
private static void _CollectOperationFunction(PromoteOperationFunction operation, string name)
=> AddOperationFunction(name, operation);
[LifecycleStart]
private static void _Start()
{
var args = Basics.CommandLineArguments;
if (args is ["promote", _])
{
Context.Info("当前进程为提权进程");
IsCurrentProcessPromoted = true;
// 预定义操作
Context.Info("正在加载提权操作");
_CollectOperationFunction_InvokeInjection_Promote();
AddJsonOperationFunction<ProcessStartInfo>("start-json", _StartProcessWithInfo);
// 结束生命周期管理,启动提权操作线程
Lifecycle.PendingLogFileName = "LastPending_Promote.log";
LogWrapper.OnLog += (level, msg, module, ex) => Context.CustomLog($"[{module}] {msg}", ex, level);
Context.Info("已接管通用日志");
Context.Info("正在启动服务线程");
new Thread(() => _PerformAsPromoteProcess(args[1])) { Name = "Promote" }.Start();
Context.RequestStopLoading();
Context.DeclareStopped();
}
else
{
Context.Info("当前进程为主进程");
IsCurrentProcessPromoted = false;
// TODO 提权进程自动启动
}
}
[LifecycleStop]
private static void _Stop()
{
if (_promotePipeServer is not null)
{
Context.Debug("正在结束提权管道服务");
_promotePipeServer.Dispose();
}
if (_promoteProcess is not null && !_promoteProcess.WaitForExit(3000))
{
Context.Debug("正在结束提权进程");
ProcessInterop.Kill(_promoteProcess, 0, true);
}
}
}
@@ -0,0 +1,9 @@
namespace PCL.Core.App.Essentials;
/// <summary>
/// RPC 函数<br/>
/// 接收参数并返回响应内容
/// </summary>
/// <param name="argument">参数</param>
/// <returns>响应内容</returns>
public delegate RpcResponse RpcFunction(string? argument, string? content, bool indent);
@@ -0,0 +1,58 @@
using System;
namespace PCL.Core.App.Essentials;
public class RpcPropertyOperationFailedException : Exception;
/// <summary>
/// RPC 属性<br/>
/// 大多数时候只需要使用构造方法,其他结构保留供内部使用
/// </summary>
public class RpcProperty
{
public delegate void GetValueDelegate(out string? outValue);
public event GetValueDelegate GetValue;
public delegate void SetValueDelegate(string? value, ref bool success);
public event SetValueDelegate? SetValue;
public readonly string Name;
public readonly bool Settable = true;
public string? Value
{
get
{
GetValue.Invoke(out var value);
return value;
}
set
{
var success = true;
SetValue?.Invoke(value, ref success);
if (!success)
throw new RpcPropertyOperationFailedException();
}
}
/// <param name="name">属性名称</param>
/// <param name="onGetValue">默认的 <c>GetValue</c> 回调</param>
/// <param name="onSetValue">默认的 <c>SetValue</c> 回调</param>
/// <param name="settable">指定该属性是否可更改,若该值为 <c>false</c> 的同时 <paramref name="onSetValue"/> 为 <c>null</c>,则该属性成为只读属性</param>
public RpcProperty(string name, Func<string?> onGetValue, Action<string?>? onSetValue = null, bool settable = false)
{
Name = name;
GetValue += (out outValue) => { outValue = onGetValue(); };
if (onSetValue is not null)
{
SetValue += (value, ref _) => { onSetValue(value); };
}
else if (!settable)
{
Settable = false;
SetValue += (_, ref success) => { success = false; };
}
}
}
@@ -0,0 +1,73 @@
using System;
using System.IO;
namespace PCL.Core.App.Essentials;
public enum RpcResponseStatus
{
Success,
Failure,
Err
}
public enum RpcResponseType
{
Empty,
Text,
Json,
Base64
}
/// <summary>
/// Pipe RPC 响应
/// </summary>
public class RpcResponse
{
public RpcResponseStatus Status { get; }
public RpcResponseType Type { get; }
public string? Name { get; }
public string? Content { get; }
public RpcResponse(RpcResponseStatus status, RpcResponseType type = RpcResponseType.Empty, string? content = null,
string? name = null)
{
if (content is not null && type == RpcResponseType.Empty)
throw new ArgumentException("Empty response with non-null content");
Status = status;
Type = type;
Content = content;
Name = name;
}
// STATUS type [name]
// [content]
public void Response(StreamWriter writer)
{
var nameArea = Name is null ? "" : $" {Name}";
writer.WriteLine($"{Status.ToString().ToUpperInvariant()} {Type.ToString().ToLowerInvariant()}{nameArea}");
if (Content is not null)
writer.WriteLine(Content);
}
public static readonly RpcResponse EmptySuccess = new RpcResponse(RpcResponseStatus.Success);
public static readonly RpcResponse EmptyFailure = new RpcResponse(RpcResponseStatus.Failure);
public static RpcResponse Err(string content, string? name = null)
{
return new RpcResponse(RpcResponseStatus.Err, RpcResponseType.Text, content, name);
}
public static RpcResponse Success(RpcResponseType type, string content, string? name = null)
{
return new RpcResponse(RpcResponseStatus.Success, type, content, name);
}
public static RpcResponse Failure(RpcResponseType type, string content, string? name = null)
{
return new RpcResponse(RpcResponseStatus.Failure, type, content, name);
}
}
@@ -0,0 +1,276 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using PCL.Core.App.IoC;
using PCL.Core.IO;
namespace PCL.Core.App.Essentials;
/// <summary>
/// 用于终止 Pipe RPC 执行过程并返回错误信息的异常<br/>
/// 当抛出该异常时 RPC 服务端将会返回内容为 <c>Reason</c> 的 <c>ERR</c> 响应
/// </summary>
public class RpcException(string reason) : Exception
{
public string Reason => reason;
}
/// <summary>
/// 标记一个方法或属性,使其成为 RPC 函数/属性。<br/>
/// 方法签名需兼容 <see cref="RpcFunction"/> 委托,属性仅支持 <see langword="string"/> 类型。
/// </summary>
/// <param name="name">该 RPC 函数/属性的名称</param>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
[DependencyCollector<RpcFunction>("rpc-function", AttributeTargets.Method)]
[DependencyCollector<string>("rpc-property", AttributeTargets.Property)]
public sealed class RegisterRpc(string name) : Attribute;
/// <summary>
/// RPC 服务项
/// </summary>
[LifecycleService(LifecycleState.Loaded)]
[LifecycleScope("rpc", "远程执行服务")]
public sealed partial class RpcService
{
private NamedPipeServerStream? _pipe;
[LifecycleStart]
private void _Start()
{
_pipe = PipeComm.StartPipeServer("Echo", _EchoPipeName, _EchoPipeCallback);
}
[LifecycleStop]
private async Task _StopAsync()
{
if (_pipe is not null) await _pipe.DisposeAsync();
}
[LifecycleDependencyInjection("rpc-function", AttributeTargets.Method)]
private static void _CollectRpcFunctionRegistry(ImmutableList<(RpcFunction func, string name)> items)
{
foreach (var (func, name) in items)
{
AddFunction(name, func);
}
}
[LifecycleDependencyInjection("rpc-property", AttributeTargets.Property)]
private static void _CollectRpcPropertyRegistry(ImmutableList<(PropertyAccessor<string> prop, string name)> items)
{
foreach (var (prop, name) in items)
{
AddProperty(new RpcProperty(
name,
() => prop.Value,
value => prop.Value = value ?? "",
prop.CanSet
));
}
}
public const string PipePrefix = "PCLCE_RPC";
private static readonly string _EchoPipeName = $"{PipePrefix}@{Basics.CurrentProcess.Id}";
private static readonly string[] _RequestTypeArray = ["GET", "SET", "REQ"];
private static readonly HashSet<string> _RequestType = [.._RequestTypeArray];
#region Property
private static readonly Dictionary<string, RpcProperty> _PropertyMap = new();
/// <summary>
/// 添加一个新的 RPC 属性,若有多个使用 foreach 即可
/// </summary>
/// <param name="prop">要添加的属性</param>
/// <returns>是否成功添加(若已存在相同名称的属性则无法添加)</returns>
public static bool AddProperty(RpcProperty prop) => _PropertyMap.TryAdd(prop.Name, prop);
/// <summary>
/// 通过指定的名称删除已存在的 RPC 属性
/// </summary>
/// <param name="name">属性名称</param>
/// <returns>是否成功删除(若不存在该名称则无法删除)</returns>
public static bool RemoveProperty(string name)
{
return _PropertyMap.Remove(name);
}
/// <summary>
/// 删除已存在的 RPC 属性,实质上仍然是通过属性的名称删除,但会检查是否是同一个对象
/// </summary>
/// <param name="prop">要删除的属性</param>
/// <returns></returns>
public static bool RemoveProperty(RpcProperty prop)
{
var key = prop.Name;
var result = _PropertyMap.TryGetValue(key, out var value);
if (!result || value != prop) return false;
_PropertyMap.Remove(key);
return true;
}
#endregion
#region Function
private static readonly Dictionary<string, RpcFunction> _FunctionMap = new() {
["ping"] = ((_, _, _) => RpcResponse.EmptySuccess),
["activate"] = ((_, _, _) =>
{
if (Lifecycle.CurrentState >= LifecycleState.WindowCreated) ActivateMainWindow();
else Lifecycle.When(LifecycleState.WindowCreated, ActivateMainWindow);
return RpcResponse.EmptySuccess;
void ActivateMainWindow()
{
var app = Lifecycle.CurrentApplication;
app.Dispatcher.BeginInvoke(() =>
{
var window = app.MainWindow!;
if (window.WindowState == WindowState.Minimized) window.WindowState = WindowState.Normal;
if (!window.Topmost)
{
window.Topmost = true;
window.Topmost = false;
}
window.Activate();
});
}
})
};
/// <summary>
/// 添加一个新的 RPC 函数,若有多个使用 foreach 即可
/// </summary>
/// <param name="name">函数名称</param>
/// <param name="func">函数过程</param>
/// <returns>是否成功添加(若已存在相同名称的函数则无法添加)</returns>
public static bool AddFunction(string name, RpcFunction func) => _FunctionMap.TryAdd(name, func);
/// <summary>
/// 通过指定的名称删除已存在的 RPC 函数
/// </summary>
/// <param name="name">函数名称</param>
/// <returns>是否成功删除(若不存在该名称则无法删除)</returns>
public static bool RemoveFunction(string name)
{
return _FunctionMap.Remove(name);
}
#endregion
private static bool _EchoPipeCallback(StreamReader reader, StreamWriter writer, Process? client)
{
try
{
// GET/SET/REQ [target]
// [content]
var header = reader.ReadLine(); // 读入请求头
Context.Info($"客户端请求: {header}");
var args = header?.Split([' '], 2) ?? []; // 分离请求类型和参数
if (args.Length < 2 || args[1].Length == 0) throw new RpcException("请求参数过少");
var type = args[0].ToUpperInvariant();
if (!_RequestType.Contains(type)) throw new RpcException($"请求类型必须为 {string.Join("/", _RequestTypeArray)} 其中之一");
var target = args[1];
// 读入请求内容(可能没有)
var buffer = new StringBuilder();
var tmp = reader.Read();
while (tmp != PipeComm.PipeEndingChar)
{
buffer.Append((char)tmp);
tmp = reader.Read();
}
var content = buffer.Length == 0 ? null : buffer.ToString();
switch (type)
{
case "GET": case "SET": {
target = target.ToLowerInvariant();
var result = _PropertyMap.TryGetValue(target, out var prop);
if (!result) throw new RpcException($"不存在属性 {target}");
RpcResponse response;
if (type == "GET")
{
try
{
var value = prop!.Value;
response = new RpcResponse(RpcResponseStatus.Success, RpcResponseType.Text, value, target);
Context.Trace($"返回值: {value}");
}
catch (RpcPropertyOperationFailedException)
{
response = RpcResponse.EmptyFailure;
Context.Debug("设置失败: 只写属性或请求被拒绝");
}
}
else if (prop!.Settable)
{
try
{
prop.Value = content;
response = RpcResponse.EmptySuccess;
Context.Trace($"设置成功: {content}");
}
catch (RpcPropertyOperationFailedException)
{
response = RpcResponse.EmptyFailure;
Context.Debug("设置失败: 请求被拒绝");
}
}
else
{
response = RpcResponse.EmptyFailure;
Context.Debug("设置失败: 只读属性");
}
response.Response(writer);
break;
}
case "REQ": {
var targetArgs = target.Split([' '], 2); // 分离函数名和参数
var name = targetArgs[0].ToLowerInvariant();
var indent = false; // 检测缩进指示
if (name.EndsWith('$'))
{
indent = true;
name = name[..^1];
}
var result = _FunctionMap.TryGetValue(name, out var func);
if (!result) throw new RpcException($"不存在函数 {name}");
string? argument = null;
if (targetArgs.Length > 1)
argument = targetArgs[1];
Context.Trace($"正在调用函数 {name} {argument}");
var response = func!(argument, content, indent);
response.Response(writer);
Context.Trace($"函数已退出,返回状态 {response.Status}");
break;
}
}
}
catch (Exception ex)
{
if (ex is RpcException rpcEx)
{
var reason = rpcEx.Reason;
RpcResponse.Err(reason).Response(writer);
Context.Info($"出错: {reason}");
}
else
{
RpcResponse.Err(ex.ToString(), "stacktrace").Response(writer);
Context.Error("处理请求时发生异常", ex);
}
}
return true;
}
}
@@ -0,0 +1,70 @@
using System;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Text.Json;
using PCL.Core.App.IoC;
using PCL.Core.IO;
using PCL.Core.Utils;
namespace PCL.Core.App.Essentials;
[LifecycleService(LifecycleState.BeforeLoading, Priority = -2134567890)]
[LifecycleScope("single-instance", "单例", false)]
public sealed partial class SingleInstanceService
{
private static FileStream? _lockStream;
private static readonly string _LockFilePath = Path.Combine(Paths.SharedLocalData, "instance.lock");
private static void _TryRpc(string processId, string content)
{
var pipeName = $"{RpcService.PipePrefix}@{processId}";
using var pipe = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut);
pipe.Connect(1000);
using var sw = new StreamWriter(pipe, PipeComm.PipeEncoding);
sw.WriteLine(content);
sw.Write(PipeComm.PipeEndingChar);
sw.Flush();
}
[LifecycleStart]
private static void _Start()
{
try
{
var stream = File.Open(_LockFilePath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
Context.Debug("未发现重复实例,正在向单例锁写入信息");
using var sw = new StreamWriter(stream, Encoding.ASCII, 8, true);
sw.Write(Basics.CurrentProcessId);
sw.Flush();
_lockStream = stream;
}
catch (Exception)
{
try
{
using var stream = File.Open(_LockFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var reader = new StreamReader(stream);
var pid = reader.ReadToEnd();
Context.Info($"发现重复实例 {pid},尝试传递参数并拉起主窗口");
try
{
_TryRpc(pid, "REQ cli\n" + JsonSerializer.Serialize(StartupService.UnhandledCommands, JsonCompat.SerializerOptions));
_TryRpc(pid, "REQ activate");
}
catch (Exception ex) { Context.Warn("RPC 通信失败", ex); }
}
catch (Exception ex) { Context.Error("读取单例锁出错", ex); }
finally { Context.RequestExit(1); }
}
}
[LifecycleStop]
private static void _Stop()
{
if (_lockStream is null) return;
Context.Debug("正在删除单例锁");
_lockStream.Dispose();
File.Delete(_LockFilePath);
}
}
@@ -0,0 +1,159 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using PCL.Core.App.Cli;
using PCL.Core.App.IoC;
using PCL.Core.Utils.OS;
using PCL.Core.Utils.Secret;
using PCL.Core.Utils;
namespace PCL.Core.App.Essentials;
/// <summary>
/// 命令处理委托
/// </summary>
/// <param name="model">命令模型</param>
/// <param name="isCallback">
/// 指示该委托是否由已注册的回调触发,若是,则代表可能由 RPC
/// 或用户后续操作而非当前进程的命令行参数触发,应注意鉴权问题
/// </param>
public delegate void CommandHandler(CommandLine model, bool isCallback);
[LifecycleService(LifecycleState.BeforeLoading, Priority = int.MaxValue)]
[LifecycleScope("startup", "基本信息", false)]
public sealed partial class StartupService
{
private static Exception _GetUninitializedException() => new InvalidOperationException("Not initialized");
/// <summary>
/// 解析后的命令行模型实例
/// </summary>
/// <exception cref="Exception">尚未初始化完成</exception>
public static CommandLine CommandLine
{
get => field ?? throw _GetUninitializedException();
private set;
} = null!;
private static readonly Dictionary<string, CommandLine> _UnhandledCommandMap = [];
private static readonly ConcurrentDictionary<string, CommandHandler> _HandleCallbackMap = [];
/// <summary>
/// 未处理的子命令
/// </summary>
public static IReadOnlyDictionary<string, CommandLine> UnhandledCommands => _UnhandledCommandMap.AsReadOnly();
/// <summary>
/// 处理一个子命令
/// </summary>
/// <param name="command">子命令</param>
/// <param name="handler">用于处理命令的委托,传入 <see langword="null"/> 则触发已注册的处理回调</param>
/// <param name="registerCallback">指定是否注册该委托为处理回调</param>
/// <returns>是否执行成功,若子命令不存在或未注册任何处理回调则不成功</returns>
public static bool TryHandleCommand(
string command,
CommandHandler? handler = null,
bool registerCallback = false)
{
var isCallback = false;
if (handler is null)
{
_HandleCallbackMap.TryGetValue(command, out handler);
if (handler is null) return false;
isCallback = true;
}
else if (registerCallback) _HandleCallbackMap.TryAdd(command, handler);
lock (_UnhandledCommandMap)
{
_UnhandledCommandMap.TryGetValue(command, out var model);
if (model is null) return false;
// remove all related commands
foreach (var x in _UnhandledCommandMap.Keys.Where(x => x.StartsWith(command)).ToList())
_UnhandledCommandMap.Remove(x);
// run handler
try { handler(model, isCallback); }
catch (Exception ex) { Context.Warn($"Exception thrown while handle command: {command}", ex); }
return true;
}
}
[LifecycleStart]
private static void _LogBasicInfo()
{
var info = new StringBuilder();
info.Append("\n版本: ").Append(Basics.Metadata.Version).Append(" (").Append(GetArchitectureName(RuntimeInformation.ProcessArchitecture)).Append(')');
info.Append("\n路径: ").Append(Basics.ExecutablePath);
info.Append("\n命令行参数:");
if (Basics.CommandLineArguments.Length == 0) info.Append(" []");
else foreach (var x in Basics.CommandLineArguments) info.Append("\n - ").Append(x);
info.Append("\n系统版本: ").Append(Environment.OSVersion.Version).Append(" (").Append(GetArchitectureName(RuntimeInformation.OSArchitecture)).Append(')');
var memory = KernelInterop.GetPhysicalMemoryBytes();
const int memoryDiv = 1024 * 1024;
info.Append("\n可用内存: ").Append(memory.Available / memoryDiv).Append('/').Append(memory.Total / memoryDiv).Append(" MiB");
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var cp = Encoding.GetEncoding(0);
info.Append("\n默认代码页: ").Append(cp.EncodingName).Append(" (").Append(cp.CodePage).Append(')');
info.Append("\n管理员身份: ").Append(ProcessInterop.IsAdmin());
info.Append("\n识别码: ").Append(Identify.LauncherId);
Context.Info(info.ToString());
return;
string GetArchitectureName(Architecture arch) => arch switch
{
Architecture.X64 => "x64",
Architecture.Arm64 => "ARM64",
_ => arch.ToString()
};
}
[LifecycleStart]
private static void _ParseCommandLineArgs()
{
IEnumerable<SubcommandDefinition> subcommands = [
("update", [("execute"), ("success"), ("failed")]),
("activate", []),
("promote", []),
];
Context.Debug("正在解析命令行参数...");
var c = CommandLine.Parse(Basics.FullCommandLineArguments, subcommands);
var prefix = new StringBuilder();
while (true)
{
_UnhandledCommandMap[prefix.ToString()] = c;
if (c.Subcommand is null) break;
if (prefix.Length != 0) prefix.Append('.');
prefix.Append(c.Subcommand.CommandText);
c = c.Subcommand;
}
_UnhandledCommandMap.Remove("", out c!);
CommandLine = c;
}
[RegisterRpc("cli")]
public static RpcResponse OnRpcCommand(string? argument, string? content, bool indent)
{
if (content is null) return RpcResponse.Err("Must provide valid JSON model");
try
{
var models = JsonSerializer.Deserialize<Dictionary<string, CommandLine>>(content, JsonCompat.SerializerOptions);
if (models is null) return RpcResponse.Err("Invalid JSON: empty/null content");
Task.Run(() =>
{
foreach (var (command, model) in models)
{
_UnhandledCommandMap[command] = model;
TryHandleCommand(command);
}
});
}
catch (JsonException ex)
{
return RpcResponse.Err($"Invalid JSON: {ex.Message}");
}
return RpcResponse.EmptySuccess;
}
}
@@ -0,0 +1,29 @@
using System;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Media;
namespace PCL.Core.App.Essentials;
public static class StartupValidation
{
/// <summary>
/// 确保 WPF 字体渲染环境正常(修复缺失 %windir% 环境变量导致的字体渲染异常 #3555)
/// </summary>
public static void EnsureWpfFont()
{
try
{
_ = new FormattedText("", CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
Fonts.SystemTypefaces.First(), 96d, Brushes.Black, 96d);
}
catch (UriFormatException)
{
Environment.SetEnvironmentVariable("windir", Environment.GetEnvironmentVariable("SystemRoot"),
EnvironmentVariableTarget.User);
_ = new FormattedText("", CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
Fonts.SystemTypefaces.First(), 96d, Brushes.Black, 96d);
}
}
}
@@ -0,0 +1,228 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.Win32;
using PCL.Core.App.IoC;
using PCL.Core.IO.Net;
using PCL.Core.IO.Net.Dns;
using PCL.Core.Logging;
using PCL.Core.Utils.OS;
using STUN.Client;
using Sentry;
using Sentry.Extensibility;
namespace PCL.Core.App.Essentials;
[LifecycleScope("telemetry", "遥测")]
[LifecycleService(LifecycleState.Running)]
public sealed partial class TelemetryService
{
private static void _InitSentry()
{
Context.Info("开始初始化 Sentry SDK");
var dsn = EnvironmentInterop.GetSecret("SENTRY_DSN");
if (dsn is null)
{
Context.Warn("未找到 Sentry DSN");
return;
}
var release = $"{Basics.VersionName}";
#if DEBUG
var environment = "Debug";
#else
var environment = "Production";
#endif
SentrySdk.Init(options =>
{
options.Dsn = dsn;
#if DEBUG
options.Debug = true;
#else
options.Debug = false;
#endif
options.SendDefaultPii = false;
options.IsGlobalModeEnabled = true;
options.AutoSessionTracking = true;
options.Release = release;
options.Environment = environment;
// 应该被直接丢弃不上报的异常类型
options.AddExceptionFilterForType<TimeoutException>();
options.AddExceptionFilterForType<HttpRequestException>();
options.AddExceptionFilterForType<WebException>();
options.AddExceptionFilterForType<TaskCanceledException>();
options.AddExceptionFilterForType<DirectoryNotFoundException>();
options.AddExceptionFilterForType<UnauthorizedAccessException>();
options.AddExceptionFilterForType<FileNotFoundException>();
// 细分类型的过滤器
options.AddExceptionFilter(new SocketExceptionFilter());
options.SetBeforeSend(@event => @event.Level is SentryLevel.Debug ? null : @event);
});
SentrySdk.ConfigureScope(scope =>
{
scope.User = new SentryUser
{
Id = Utils.Secret.Identify.LauncherId
};
});
Context.Info("Sentry SDK 初始化完成");
}
// 错误上报
public static void ReportException(Exception ex, string plain, LogLevel level)
{
var sentryEvent = new SentryEvent(ex)
{
Level = level.RealLevel() switch
{
LogLevel.Fatal => SentryLevel.Fatal,
LogLevel.Error => SentryLevel.Error,
LogLevel.Warning => SentryLevel.Warning,
LogLevel.Info => SentryLevel.Info,
LogLevel.Debug or LogLevel.Trace => SentryLevel.Debug,
_ => throw new ArgumentOutOfRangeException(nameof(level))
}
};
if (!string.IsNullOrWhiteSpace(plain))
{
sentryEvent.Message = new SentryMessage { Formatted = plain };
}
SentrySdk.CaptureEvent(sentryEvent);
}
// 设备环境上报
private static void _ReportDeviceEnvironment(TelemetryDeviceEnvironment content)
{
Context.Info("正在上报设备环境调查数据");
SentrySdk.ConfigureScope(scope =>
{
scope.Contexts["Telemetry"] = content;
});
try
{
SentrySdk.CaptureMessage("设备环境调查");
Context.Info("已发送设备环境调查数据");
}
catch(Exception ex)
{
Context.Error("设备环境调查数据发送失败,请检查网络连接以及使用的版本", ex);
}
}
// ReSharper disable UnusedAutoPropertyAccessor.Local
private class TelemetryDeviceEnvironment
{
public required string Tag { get; set; }
public required string Id { get; set; }
[JsonPropertyName("OS")] public required int Os { get; set; }
public required bool Is64Bit { get; set; }
[JsonPropertyName("IsARM64")] public required bool IsArm64 { get; set; }
public required string Launcher { get; set; }
public required string LauncherBranch {get; set; }
[JsonPropertyName("UsedOfficialPCL")] public required bool UsedOfficialPcl { get; set; }
[JsonPropertyName("UsedHMCL")] public required bool UsedHmcl { get; set; }
[JsonPropertyName("UsedBakaXL")] public required bool UsedBakaXl { get; set; }
public required ulong Memory { get; set; }
public required string? NatMapBehaviour { get; set; }
public required string? NatFilterBehaviour { get; set; }
[JsonPropertyName("IPv6Status")] public required string Ipv6Status { get; set; }
}
// ReSharper disable once InconsistentNaming
private const string STUN_SERVER_ADDR = "stun.miwifi.com";
// ReSharper restore UnusedAutoPropertyAccessor.Local
[LifecycleStart]
private static async Task _StartAsync()
{
if (!Config.System.Telemetry) return;
_InitSentry();
var appDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
// stun test
StunClient5389UDP? natTest = null;
var miWifiIps = await DnsQuery.Instance.QueryForIpAsync(STUN_SERVER_ADDR).ConfigureAwait(false);
try
{
miWifiIps ??= await Dns.GetHostAddressesAsync(STUN_SERVER_ADDR).ConfigureAwait(false);
} catch(Exception) { /* Ignore dns error */ }
if (miWifiIps is not null && miWifiIps.Length != 0)
{
natTest = new StunClient5389UDP(new IPEndPoint(miWifiIps.First(), 3478),
new IPEndPoint(IPAddress.Any, 0));
await natTest.QueryAsync().ConfigureAwait(false);
}
var telemetry = new TelemetryDeviceEnvironment
{
Tag = "Telemetry",
Id = Utils.Secret.Identify.LauncherId,
Os = Environment.OSVersion.Version.Build,
Is64Bit = Environment.Is64BitOperatingSystem,
IsArm64 = RuntimeInformation.OSArchitecture.Equals(Architecture.Arm64),
Launcher = Basics.VersionName,
LauncherBranch = Config.Update.UpdateChannel switch
{
UpdateChannel.Release => "Release",
UpdateChannel.Beta => "Beta",
UpdateChannel.Dev => "Dev",
_ => "Unknown"
},
UsedOfficialPcl =
bool.TryParse(Registry.GetValue(@"HKEY_CURRENT_USER\Software\PCL", "SystemEula", "false") as string,
out var officialPcl) && officialPcl,
UsedHmcl = Directory.Exists(Path.Combine(appDataFolder, ".hmcl")),
UsedBakaXl = Directory.Exists(Path.Combine(appDataFolder, "BakaXL")),
Memory = KernelInterop.GetPhysicalMemoryBytes().Total,
NatMapBehaviour = natTest?.State.MappingBehavior.ToString(),
NatFilterBehaviour = natTest?.State.FilteringBehavior.ToString(),
Ipv6Status = NetworkInterfaceUtils.GetIPv6Status().ToString()
};
_ReportDeviceEnvironment(telemetry);
}
// 用来细分过滤 SocketException 的过滤器,我觉得应该除了遥测服务之外没有其他东西会用到这破玩意儿
private sealed class SocketExceptionFilter : IExceptionFilter
{
public bool Filter(Exception ex)
{
if (ex is SocketException socketEx)
{
return socketEx.SocketErrorCode is
SocketError.ConnectionRefused or
SocketError.TimedOut or
SocketError.HostNotFound or
SocketError.NetworkUnreachable or
SocketError.ConnectionReset;
}
return false;
}
}
[LifecycleStop]
private static void _StopAsync()
{
SentrySdk.Close();
}
}
@@ -0,0 +1,48 @@
using System;
using System.Globalization;
using System.IO;
using System.Threading;
namespace PCL.Core.App.Essentials;
public static class UpdateHelper
{
/// <summary>
/// 更新启动器(替换文件)
/// </summary>
/// <param name="source">用于替换的来源文件路径</param>
/// <param name="target">目标文件路径</param>
public static Exception? Replace(string source, string target)
{
var backup = $"{target}.bak.{DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture)}";
Exception? lastEx = null;
try
{
source = Path.GetFullPath(source);
target = Path.GetFullPath(target);
// 备份目标文件
File.Copy(target, backup);
if (!File.Exists(backup)) throw new FileNotFoundException("备份目标文件失败", backup);
// 删除原文件并等待文件删除事件
var watcher = new FileSystemWatcher(Basics.GetParentPathOrEmpty(target), Path.GetFileName(target));
var deletedEvent = new ManualResetEventSlim(false);
watcher.Deleted += (_, _) => deletedEvent.Set();
watcher.EnableRaisingEvents = true;
File.Delete(target);
if (!deletedEvent.Wait(TimeSpan.FromSeconds(3))) throw new TimeoutException("删除目标文件失败");
watcher.EnableRaisingEvents = false;
watcher.Dispose();
// 复制到目标文件
File.Copy(source, target);
if (!File.Exists(target)) throw new FileNotFoundException("复制到目标文件失败", target);
}
catch (Exception ex)
{
// 出错:恢复原文件并返回异常
if (File.Exists(backup) && !File.Exists(target)) File.Move(backup, target);
lastEx = ex;
}
if (File.Exists(backup)) File.Delete(backup); // 删除备份文件
return lastEx;
}
}
@@ -0,0 +1,91 @@
using System;
using System.Diagnostics;
using System.IO;
using PCL.Core.App.IoC;
using PCL.Core.Utils.Exts;
namespace PCL.Core.App.Essentials;
[LifecycleService(LifecycleState.BeforeLoading)]
public sealed class UpdateService : GeneralService
{
private static LifecycleContext? _context;
private static LifecycleContext Context => _context!;
private UpdateService() : base("update", "更新", false) { _context = ServiceContext; }
public override void Start()
{
var args = Basics.CommandLineArguments;
if (args is not ["update", _, _, _, _])
{
switch (args)
{
case ["update_finished", _]:
{
var toDelete = args[1];
File.Delete(toDelete);
Context.Debug("更新来源文件已删除");
break;
}
case ["update_failed", _]:
{
var reason = args[1];
Context.Error(
$"更新失败: {reason}\n你可以手动将 exe 文件替换为 PCL 目录中的新版本" +
$"或再次尝试更新,若再次尝试仍然失败,请尽快反馈这个问题");
break;
}
default: Context.Debug("无更新任务"); break;
}
Context.DeclareStopped();
return;
}
try
{
Context.Info("开始更新");
Lifecycle.PendingLogDirectory = Path.Combine(Basics.ExecutableDirectory, "Log");
Lifecycle.PendingLogFileName = "LastPending_Update.log";
var oldProcessId = args[1].Convert<int>();
Context.Debug($"旧版本进程 ID: {oldProcessId}");
try
{
var oldProcess = Process.GetProcessById(oldProcessId);
Context.Debug("正在等待旧版本进程退出");
oldProcess.WaitForExit();
Context.Trace("旧版本进程已退出");
}
catch
{
/* ignored */
}
Context.Debug("正在替换文件");
var target = args[2];
Context.Trace($"目标: {target}");
var source = args[3];
Context.Trace($"来源: {source}");
var ex = UpdateHelper.Replace(source, target);
if (ex is null) Context.Trace("替换完成");
else Context.Error("替换文件出错", ex);
var restart = args[4].Convert<bool>();
if (restart)
{
var restartArgs = (ex is null) ? $"finished \"{source}\"" : $"failed \"{ex.Message}\"";
restartArgs = $"update_{restartArgs}";
Context.Debug($"重启中,使用参数: {restartArgs}");
Process.Start(target, restartArgs);
}
}
catch (Exception ex)
{
Context.Error("更新过程出错", ex);
}
Context.RequestExit();
}
}
@@ -0,0 +1,261 @@
using PCL.Core.App.IoC;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace PCL.Core.App.EventBus;
[LifecycleService(LifecycleState.BeforeLoading)]
[LifecycleScope("eventbus", "EventBus")]
public sealed partial class EventBusService
{
private static readonly ConcurrentDictionary<string,
ConcurrentDictionary<Type, ConcurrentDictionary<Guid, (Func<EventDataBase, Task> Handler, WeakReference<object>? OwnerRef)>>> _Channels = [];
/// <summary>
/// 0 = running, 1 = stopping/closed
/// </summary>
private static int _isStopping;
[LifecycleStop]
private static Task _StopAsync()
{
Interlocked.Exchange(ref _isStopping, 1);
try
{
var channelCount = _Channels.Count;
var handlerCount = _Channels.Values.Sum(c => c.Values.Sum(h => h.Count));
_Channels.Clear();
Context.Info($"EventBus stopping: cleared {channelCount} channels and {handlerCount} handlers.");
return Task.CompletedTask;
}
catch (Exception exception)
{
Context.Error($"Exception while stopping EventBus: {exception}");
return Task.FromException(exception);
}
}
/// <summary>
/// Publish an event to a channel. All handlers subscribed to this channel with compatible event data type will be invoked.
/// </summary>
/// <exception cref="InvalidOperationException">EventBus is stopping</exception>
public static Task PublishAsync<TEventData>(string channelName, TEventData data) where TEventData : EventDataBase
{
if (Volatile.Read(ref _isStopping) != 0) throw new InvalidOperationException("EventBus is stopping");
return _CallChannelAsync(channelName, data);
}
/// <summary>
/// 订阅使用 <c>IEventHandler{TEventData}</c> 的对象实例。
/// 返回 <see cref="IDisposable"/> 用于取消订阅。
/// </summary>
/// <exception cref="InvalidOperationException">EventBus is stopping</exception>
/// <exception cref="InvalidOperationException">Failed to create channel</exception>
/// <exception cref="ArgumentNullException"><paramref name="channel"/> is <see langword="null"/></exception>
public static IDisposable Subscribe<TEventData>(string channel, IEventHandler<TEventData> handler, bool disposeOwnerOnUnsubscribe = false)
where TEventData : EventDataBase
{
if (Volatile.Read(ref _isStopping) != 0) throw new InvalidOperationException("EventBus is stopping");
if (string.IsNullOrWhiteSpace(channel)) throw new ArgumentNullException(nameof(channel));
if (handler is null) throw new ArgumentNullException(nameof(handler));
if (!_Channels.TryGetValue(channel, out var dataHandler))
{
Context.Trace($"Channel {channel} not found.");
//throw new InvalidOperationException("No channel found for the given channel identification.");
// create channel if not exist
var success = AddChannel(channel);
if (!success)
{
throw new InvalidOperationException("Failed to create channel.");
}
}
dataHandler ??= _Channels[channel]; // ensure dataHandler is not null here
var dataType = typeof(TEventData);
var handlers = dataHandler.GetOrAdd(dataType, _ => []);
var ownerRef = new WeakReference<object>(handler);
var id = Guid.NewGuid();
handlers.TryAdd(id, (Wrapper, ownerRef));
return new Subscription(() =>
{
handlers.TryRemove(id, out _);
if (handlers.IsEmpty)
{
dataHandler.TryRemove(dataType, out _);
}
// disposal responsibility: if this subscription requested owner disposal, try to dispose target if still alive
if (disposeOwnerOnUnsubscribe &&
ownerRef.TryGetTarget(out var tgt) &&
tgt is IDisposable d)
{
// check if any remaining subscription in this channel still references the same owner
var stillReferenced = dataHandler.Values.Any(dict => dict.Values.Any(e => e.OwnerRef is not null && e.OwnerRef.TryGetTarget(out var other) && ReferenceEquals(other, tgt)));
if (stillReferenced) return;
try { d.Dispose(); } catch (Exception ex) { Context.Error($"Exception disposing subscription owner: {ex}"); }
}
});
Task Wrapper(EventDataBase ev)
{
if (ownerRef.TryGetTarget(out var target) && target is IEventHandler<TEventData> typed)
{
return typed.HandleEventAsync((TEventData)ev);
}
return Task.CompletedTask;
}
}
/// <summary>
/// 订阅一个委托
/// </summary>
/// <exception cref="InvalidOperationException">EventBus is stopping</exception>
/// <exception cref="InvalidOperationException">Failed to create channel</exception>
/// <exception cref="ArgumentNullException"><paramref name="channel"/> is <see langword="null"/></exception>
public static IDisposable Subscribe<TEventData>(string channel, Func<TEventData, Task> handler)
where TEventData : EventDataBase
{
if (Volatile.Read(ref _isStopping) != 0) throw new InvalidOperationException("EventBus is stopping");
if (string.IsNullOrWhiteSpace(channel)) throw new ArgumentNullException(nameof(channel));
if (handler is null) throw new ArgumentNullException(nameof(handler));
if (!_Channels.TryGetValue(channel, out var dataHandler))
{
Context.Trace($"Channel {channel} not found.");
//throw new InvalidOperationException("No channel found for the given channel identification.");
// create channel if not exist
var success = AddChannel(channel);
if (!success)
{
throw new InvalidOperationException("Failed to create channel.");
}
}
dataHandler ??= _Channels[channel]; // ensure dataHandler is not null here
var dataType = typeof(TEventData);
var handlers = dataHandler.GetOrAdd(dataType, _ => []);
var id = Guid.NewGuid();
handlers.TryAdd(id, (Wrapper, null));
return new Subscription(() =>
{
handlers.TryRemove(id, out _);
if (handlers.IsEmpty)
{
dataHandler.TryRemove(dataType, out _);
}
});
Task Wrapper(EventDataBase ev) => handler((TEventData)ev);
}
/// <summary>
/// 创建 channel(显式)
/// </summary>
public static bool AddChannel(string name) => !string.IsNullOrWhiteSpace(name) && _Channels.TryAdd(name, []);
/// <summary>
/// Remove a channel and all its handlers. Use with caution.
/// </summary>
/// <param name="name">Channel name.</param>
/// <returns><see langword="true"/> if the channel was removed; otherwise, <see langword="false"/>.</returns>
public static bool RemoveChannel(string name) => _Channels.TryRemove(name, out _);
private static Task _CallChannelAsync<TEventData>(string channel, TEventData data)
where TEventData : EventDataBase
{
if (!_Channels.TryGetValue(channel, out var eventHandlers))
{
Context.Error($"Channel {channel} not found.");
throw new InvalidOperationException("No channel found for the given channel identification.");
}
return _CallEventHandlerAsync(data, eventHandlers);
}
private static Task _CallEventHandlerAsync<TEventData>(TEventData data,
ConcurrentDictionary<Type, ConcurrentDictionary<Guid, (Func<EventDataBase, Task> Handler, WeakReference<object>? OwnerRef)>> dataHandlers)
where TEventData : EventDataBase
{
var eventType = data.GetType();
var matching = new List<Func<EventDataBase, Task>>();
foreach (var (registeredType, handlers) in dataHandlers)
{
if (registeredType.IsAssignableFrom(eventType))
{
foreach (var kv in handlers.ToImmutableArray())
{
var key = kv.Key;
var entry = kv.Value;
if (entry.OwnerRef is not null)
{
if (!entry.OwnerRef.TryGetTarget(out var _))
{
// owner was collected, remove this subscription
handlers.TryRemove(key, out _);
continue;
}
}
matching.Add(entry.Handler);
}
}
}
if (matching.Count == 0)
{
Context.Trace($"No handler found for event data type {eventType.Name}");
return Task.CompletedTask;
// will not throw Exception
//throw new InvalidOperationException("No handler found for the given event data type.");
}
var tasks = matching.Select(async h =>
{
try
{
await h(data).ConfigureAwait(false);
}
catch (Exception ex)
{
Context.Error($"Event handler threw an exception: {ex}");
}
}).ToImmutableArray();
return Task.WhenAll(tasks);
}
private sealed class Subscription : IDisposable
{
private Action? _dispose;
/// <exception cref="ArgumentNullException">The dispose action is null.</exception>
public Subscription(Action dispose) => _dispose = dispose ?? throw new ArgumentNullException(nameof(dispose));
/// <exception cref="Exception">A delegate callback throws an exception.</exception>
public void Dispose()
{
var d = Interlocked.Exchange(ref _dispose, null);
d?.Invoke();
}
}
}
@@ -0,0 +1,5 @@
using System;
namespace PCL.Core.App.EventBus;
public record EventDataBase(Guid Id, string Name);
@@ -0,0 +1,14 @@
using System;
using System.Threading.Tasks;
namespace PCL.Core.App.EventBus;
public interface IEventHandler<in TEventData> : IDisposable
where TEventData : EventDataBase
{
/// <summary>
/// Handle a event with the data, and the event is published by a publisher.
/// </summary>
/// <param name="eventData">The data that published by a publisher.</param>
Task HandleEventAsync(TEventData eventData);
}
@@ -0,0 +1,13 @@
using System.Threading.Tasks;
namespace PCL.Core.App.EventBus;
// I think this is too hard to implement. So this is Obsolete.
public interface IResponsibleEventHandler<TResponse>
{
/// <summary>
/// Handle a event with the data, and the event is published by a publisher, and return a response to the publisher.
/// </summary>
/// <param name="eventData">The event data that published by a publisher</param>
Task<TResponse> HandleEventAsync(EventDataBase eventData);
}
@@ -0,0 +1,106 @@
using System;
namespace PCL.Core.App.IoC;
/// <summary>
/// 注册生命周期服务项,将由生命周期管理统一创建实例,然后在指定生命周期自动启动或加入等待手动启动列表。<br/>
/// 使用此注解的类型必须直接或间接实现 <see cref="ILifecycleService"/> 接口,否则将被忽略。
/// </summary>
/// <param name="startState">详见 <see cref="StartState"/></param>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class LifecycleServiceAttribute(LifecycleState startState) : Attribute
{
/// <summary>
/// 指定该服务项应于何种生命周期状态启动。生命周期管理将在指定的状态按照 <see cref="Priority"/> 自动启动服务项。
/// </summary>
public LifecycleState StartState { get; } = startState;
/// <summary>
/// 启动优先级。同一个生命周期状态有多个服务项需要启动时,将会按优先级数值<b>降序</b>启动,即数值越大越优先。<br/>
/// 虽然这个值可以为任意 32 位整数,但是<b>非核心服务请勿使用较为极端的值,尤其是
/// <c>int.MaxValue</c> <c>int.MinValue</c></b>,这可能导致一些核心服务的启动时机出现问题。
/// </summary>
public int Priority { get; init; } = 0;
}
/// <summary>
/// 标记一个注解,使其成为依赖收集器。<br/>
/// 使用被标记的注解标记的 <see langword="public"/> 类 与 <see langword="public"/> <see langword="static"/>
/// 方法/属性 会被作为依赖收集到一个统一的存储位置,并用于运行时的依赖注入。
/// </summary>
/// <param name="identifier">依赖标记</param>
/// <param name="targets">目标,仅支持类、方法、属性,可以使用 <c>|</c> 分隔以添加多个目标</param>
/// <typeparam name="TDependency">依赖类型</typeparam>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class DependencyCollectorAttribute<TDependency>(string identifier, AttributeTargets targets) : Attribute;
/// <summary>
/// 标记一个方法以自动生成注入操作,该方法需在 <see langword="partial"/> 类中。<br/>
/// 自动生成的注入操作将收集依赖并调用该方法以注入,该方法第一个参数必须为依赖类型,其余参数需与对应依赖收集器匹配。
/// </summary>
/// <param name="identifier">依赖标记</param>
/// <param name="lifecycleAutoInvoke">是否在生命周期启动时自动执行注入</param>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class DependencyInjectionPointAttribute(string identifier, bool lifecycleAutoInvoke = true) : Attribute;
/// <summary>
/// 标记一个 partial 类,自动实现 <see cref="ILifecycleService"/> 接口,并基于其他有标记的方法生成
/// <see cref="ILifecycleService.StartAsync"/> 和 <see cref="ILifecycleService.StopAsync"/> 方法
/// </summary>
/// <param name="identifier">See <see cref="ILifecycleService.Identifier"/></param>
/// <param name="name">See <see cref="ILifecycleService.Name"/></param>
/// <param name="asyncStart">See <see cref="ILifecycleService.SupportAsync"/></param>
[AttributeUsage(AttributeTargets.Class)]
public sealed class LifecycleScopeAttribute(string identifier, string name, bool asyncStart = true) : Attribute;
/// <summary>
/// 标记一个 Start 方法,可以标记多个
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class LifecycleStartAttribute : Attribute;
/// <summary>
/// 标记一个 Stop 方法,可以标记多个
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class LifecycleStopAttribute : Attribute;
/// <summary>
/// 标记一个命令处理器,可以标记多个,将自动识别和处理方法参数。<br/>
/// 若方法的第一个参数类型为 <see cref="PCL.Core.App.Cli.CommandLine"/> 则会传入指定指令的命令行模型。<br/>
/// 除命令行模型以外,任何参数在未显式声明默认值时均默认传入 <see langword="default"/> 值,请尽可能为所有参数提供显式默认值。<br/>
/// <b>NOTE</b>: 请勿使用异步实现,返回 Task 的方法将被直接忽略。<br/>
/// <b>NOTE</b>: 该方法可能在任意线程被调用,请注意线程上下文和同步问题。
/// <p/>示例:
/// <code>
/// [LifecycleCommandHandler("foo")]
/// private static void _FooHandler(CommandLine model, string bar, bool flag) {
/// // process logic...
/// // argument example: foo --flag --bar blablabla
/// }
/// </code>
/// </summary>
/// <param name="command">命令名</param>
[AttributeUsage(AttributeTargets.Method)]
public sealed class LifecycleCommandHandlerAttribute(string command) : Attribute;
/// <summary>
/// 标记一个依赖注入入口,可以标记多个。
/// <p/>示例:
/// <code>
/// [LifecycleDependencyInjection("some-property", AttributeTargets.Property)]
/// private static void _LoadProperties(ImmutableList&lt;(PropertyAccessor&lt;string&gt; prop, string name)&gt; items)
/// {
/// // process logic...
/// }
/// [LifecycleDependencyInjection("some-method", AttributeTargets.Method)]
/// private static void _LoadMethods(ImmutableList&lt;(Action method, string name)&gt; items)
/// {
/// // process logic...
/// }
/// </code>
/// </summary>
/// <param name="identifier">依赖标识符</param>
/// <param name="targets">依赖类型,可以使用 <c>|</c> 连接多个</param>
[AttributeUsage(AttributeTargets.Method)]
public sealed class LifecycleDependencyInjectionAttribute(string identifier, AttributeTargets targets) : Attribute;
@@ -0,0 +1,9 @@
namespace PCL.Core.App.IoC;
public abstract class DependencyArguments
{
}
public class DependencyArguments<TArguments> : DependencyArguments
{
}
@@ -0,0 +1,34 @@
using System;
using System.Collections.Immutable;
using System.Threading.Tasks;
namespace PCL.Core.App.IoC;
public abstract class DependencyGroup
{
public abstract void InvokeInjection(Delegate injection);
}
public class DependencyGroup<TValue> : DependencyGroup
{
public required ImmutableList<TValue> Items { get; init; }
public override void InvokeInjection(Delegate injection)
{
if (injection is Action<ImmutableList<TValue>> action) action(Items);
else if (injection is Func<ImmutableList<TValue>, Task> awaitableAction) awaitableAction(Items).Wait();
else throw new InvalidCastException($"Injection point signature mismatch, must be: void/Task (ImmutableList<{typeof(TValue).Name}>)");
}
}
public class DependencyGroup<TValue, TArguments> : DependencyGroup
{
public required ImmutableList<(TValue value, TArguments args)> Items { get; init; }
public override void InvokeInjection(Delegate injection)
{
if (injection is Action<ImmutableList<(TValue, TArguments)>> action) action(Items);
else if (injection is Func<ImmutableList<(TValue, TArguments)>, Task> awaitableAction) awaitableAction(Items).Wait();
else throw new InvalidCastException($"Injection point signature mismatch, must be: void/Task (ImmutableList<({typeof(TValue).Name}, {typeof(TArguments).Name})>)");
}
}
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace PCL.Core.App.IoC;
public static partial class DependencyGroups
{
private static readonly AttributeTargets[] _TargetsToTry = [
AttributeTargets.Method,
AttributeTargets.Property,
AttributeTargets.Class
];
public static bool InvokeInjection(Delegate injection, string identifier, AttributeTargets targets)
{
if (!_GroupMap.TryGetValue(identifier, out var groups)) return false;
var targetGroups = new List<DependencyGroup>();
foreach (var targetToTry in _TargetsToTry.Where(t => targets.HasFlag(t)))
{
if (groups.GetValueOrDefault(targetToTry) is not { } group) return false;
targetGroups.Add(group);
}
foreach (var group in targetGroups) group.InvokeInjection(injection);
return true;
}
}
@@ -0,0 +1,63 @@
using System.Threading.Tasks;
namespace PCL.Core.App.IoC;
/// <summary>
/// General service class for constructing <see cref="ILifecycleService"/> in a more convenient way.
/// </summary>
public abstract class GeneralService : ILifecycleService
{
/// <inheritdoc/>
public string Identifier { get; }
/// <inheritdoc/>
public string Name { get; }
/// <inheritdoc/>
public bool SupportAsync { get; }
/// <summary>
/// The context of the service instance,
/// used for declaration, logging, lifecycle operation, etc.
/// </summary>
protected LifecycleContext ServiceContext { get; }
/// <summary>
/// Initialize a general service instance.
/// This constructor should only be extended rather than invoked directly.
/// </summary>
/// <param name="identifier">see <see cref="Identifier"/></param>
/// <param name="name">see <see cref="Name"/></param>
/// <param name="asyncStart">see <see cref="SupportAsync"/></param>
protected GeneralService(string identifier, string name, bool asyncStart = true)
{
Identifier = identifier;
Name = name;
SupportAsync = asyncStart;
ServiceContext = Lifecycle.GetContext(this);
}
/// <summary>
/// Start the service, will be invoked on the specified state
/// from <see cref="LifecycleServiceAttribute.StartState"/> of <see cref="LifecycleServiceAttribute"/>.
/// </summary>
public virtual void Start() { }
/// <summary>
/// Stop the service, will be invoked while program exiting,
/// or if <see cref="Start"/> throws an exception.
/// </summary>
public virtual void Stop() { }
public Task StartAsync()
{
Start();
return Task.CompletedTask;
}
public Task StopAsync()
{
Stop();
return Task.CompletedTask;
}
}
@@ -0,0 +1,26 @@
using System.Threading.Tasks;
namespace PCL.Core.App.IoC;
/// <summary>
/// 启动器生命周期管理
/// </summary>
[LifecycleService(LifecycleState.BeforeLoading, Priority = int.MaxValue)]
public sealed partial class Lifecycle : ILifecycleService
{
public string Identifier => "lifecycle";
public string Name => "生命周期";
public bool SupportAsync => false;
private static LifecycleContext? _context;
private Lifecycle() { _context = GetContext(this); }
private static LifecycleContext Context => _context ?? SystemContext;
public Task StartAsync() => Task.CompletedTask;
public Task StopAsync()
{
_context = null;
return Task.CompletedTask;
}
}
@@ -0,0 +1,132 @@
using System;
using System.Threading.Tasks;
using PCL.Core.Logging;
namespace PCL.Core.App.IoC;
partial class Lifecycle
{
private class SystemLifecycleService : ILifecycleService
{
public string Name => "系统";
public string Identifier => "system";
public bool SupportAsync => false;
public Task StartAsync() => Task.CompletedTask;
public Task StopAsync() => Task.CompletedTask;
}
private static readonly ILifecycleService _SystemService = new SystemLifecycleService();
private static readonly LifecycleServiceInfo _SystemServiceInfo = new(_SystemService, LifecycleState.BeforeLoading);
/// <summary>
/// 系统默认上下文,无特殊需求请勿使用。
/// </summary>
internal static readonly LifecycleContext SystemContext = GetContext(_SystemService);
/// <summary>
/// 获取指定服务项对应的上下文实例用于日志输出、多任务通信等。一般情况下只推荐获取自身上下文。
/// </summary>
/// <param name="self">服务项实例</param>
/// <returns>上下文实例</returns>
public static LifecycleContext GetContext(ILifecycleService self) => new(
service: self,
onLog: item =>
{
lock (_PendingLogs)
{
if (_logService is null) _PendingLogs.Add(item);
else _PushLog(item, _logService);
}
if (item.ActionLevel == ActionLevel.MsgBoxFatal) _FatalExit();
},
onRequestExit: statusCode =>
{
if (CurrentState != LifecycleState.BeforeLoading)
throw new InvalidOperationException("只能在 BeforeLoading 时请求退出");
Context.Info($"{_ServiceName(self)} 已请求退出程序");
_Exit(statusCode);
},
onRequestRestart: args =>
{
_hasRequestedRestart = true;
_requestRestartService = self;
_requestRestartArguments = args;
},
onDeclareStopped: () =>
{
var identifier = self.Identifier;
if (GetServiceInfo(identifier)?.Identifier == identifier)
throw new InvalidOperationException("只能在服务启动阶段调用");
_DeclaredStoppedServices.Add(self);
},
onRequestStopLoading: () =>
{
if (CurrentState != LifecycleState.BeforeLoading)
throw new InvalidOperationException("只能在 BeforeLoading 时请求停止加载");
Context.Info($"{_ServiceName(self)} 已请求停止继续加载");
_hasRequestedStopLoading = true;
}
);
}
/// <summary>
/// 若要获取服务项自身的上下文实例,请使用 <see cref="Lifecycle.GetContext"/> 。
/// </summary>
public class LifecycleContext(
ILifecycleService service,
Action<LifecycleLogItem> onLog,
Action<int> onRequestExit,
Action<string?> onRequestRestart,
Action onDeclareStopped,
Action onRequestStopLoading)
{
#region
public void CustomLog(
string message,
Exception? ex = null,
LogLevel level = LogLevel.Trace,
ActionLevel? actionLevel = null
) => onLog(new LifecycleLogItem(service, message, ex, level, actionLevel ?? level.DefaultActionLevel()));
public void Trace(string message, Exception? ex = null, ActionLevel? actionLevel = null) => CustomLog(message, ex, LogLevel.Trace, actionLevel);
public void Debug(string message, Exception? ex = null, ActionLevel? actionLevel = null) => CustomLog(message, ex, LogLevel.Debug, actionLevel);
public void Info(string message, Exception? ex = null, ActionLevel? actionLevel = null) => CustomLog(message, ex, LogLevel.Info, actionLevel);
public void Warn(string message, Exception? ex = null, ActionLevel? actionLevel = null) => CustomLog(message, ex, LogLevel.Warning, actionLevel);
public void Error(string message, Exception? ex = null, ActionLevel? actionLevel = null) => CustomLog(message, ex, LogLevel.Error, actionLevel);
public void Fatal(string message, Exception? ex = null, ActionLevel? actionLevel = null) => CustomLog(message, ex, LogLevel.Fatal, actionLevel);
#endregion
/// <summary>
/// 服务项自身实例。
/// </summary>
public ILifecycleService ServiceInstance => service;
/// <summary>
/// 请求退出程序。仅可在 <see cref="LifecycleState.BeforeLoading"/> 状态调用。
/// </summary>
/// <param name="statusCode">程序返回的状态码</param>
/// <exception cref="InvalidOperationException">尝试在非 <see cref="LifecycleState.BeforeLoading"/> 状态调用</exception>
public void RequestExit(int statusCode = 0) => onRequestExit(statusCode);
/// <summary>
/// 请求在程序退出时重启。调用该方法后,程序将在正常退出流程中自动执行重启,通常与退出程序结合使用。
/// </summary>
/// <param name="arguments">重启进程时使用的命令行参数</param>
public void RequestRestartOnExit(string? arguments = null) => onRequestRestart(arguments);
/// <summary>
/// 标记自身已经结束运行。调用该方法将会直接从正在运行列表中移除该服务项,后续的
/// <c>Stop</c> 等均不会触发。仅可在服务启动阶段即 <c>Start</c> 方法结束前调用。
/// </summary>
/// <exception cref="InvalidOperationException">尝试在非启动阶段调用</exception>
public void DeclareStopped() => onDeclareStopped();
/// <summary>
/// 请求停止继续加载。多用于初始阶段在非 STA 线程中运行的服务接管进程整个生命周期,仅可在
/// <see cref="LifecycleState.BeforeLoading"/> 状态调用。
/// </summary>
/// <exception cref="InvalidOperationException">尝试在非 <see cref="LifecycleState.BeforeLoading"/> 状态调用</exception>
public void RequestStopLoading() => onRequestStopLoading();
}
@@ -0,0 +1,197 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace PCL.Core.App.IoC;
partial class Lifecycle
{
private static DateTime? _countRunningStart;
private static bool _isApplicationStarted = false;
private static bool _isLoadingStarted = false;
private static bool _isWindowCreated = false;
private static bool _hasRequestedStopLoading = false;
/// <summary>
/// [请勿调用] 处理未捕获异常流程
/// </summary>
/// <param name="ex">异常对象</param>
public static void OnException(object ex)
{
Context.Fatal("未捕获的异常", ex as Exception);
}
/// <summary>
/// [请勿调用] 程序初始化流程
/// </summary>
public static void OnInitialize()
{
// 检测重复调用
if (_isApplicationStarted) return;
_isApplicationStarted = true;
// 修改 STA 线程名
Thread.CurrentThread.Name = "STA";
// 注册全局事件
AppDomain.CurrentDomain.UnhandledException += (_, e) => OnException(e.ExceptionObject);
AppDomain.CurrentDomain.ProcessExit += (_, _) => _Exit();
TaskScheduler.UnobservedTaskException += (_, e) =>
{
Context.Error("未观测到的异步任务异常", e.Exception);
e.SetObserved();
};
// 添加系统服务
_RunningServiceInfoMap["system"] = _SystemServiceInfo;
// 实例化并存储手动服务
foreach (var service in _GetServiceTypes(LifecycleState.Manual))
{
var instance = _CreateService(service);
var identifier = instance.Identifier;
if (_ManualServiceMap.TryAdd(identifier, instance)) continue;
Context.Warn($"{_ServiceName(instance, LifecycleState.Manual)} 标识符重复,已跳过");
}
// 运行预加载服务
_StartStateFlow(LifecycleState.BeforeLoading);
if (_hasRequestedStopLoading) return;
// 运行应用程序容器
var statusCode = CurrentApplication.Run();
if (!HasShutdownStarted) _Exit(statusCode);
}
/// <summary>
/// [请勿调用] 组件加载流程
/// </summary>
public static void OnLoading()
{
// 检测重复调用
if (_isLoadingStarted) return;
_isLoadingStarted = true;
// 运行加载阶段服务
_StartStateFlow(LifecycleState.Loading, LifecycleState.WindowCreating);
// 运行窗体
CurrentApplication.MainWindow!.Show();
}
/// <summary>
/// [请勿调用] 窗口创建结束流程
/// </summary>
public static void OnWindowCreated()
{
// 检测重复调用
if (_isWindowCreated) return;
_isWindowCreated = true;
// 启动窗口流程后的服务项
_StartStateFlow(LifecycleState.WindowCreated);
_countRunningStart = DateTime.Now;
_StartWorker(LifecycleState.Running, LifecycleState.Exiting, false);
}
private static bool _hasRequestedRestart = false;
private static string? _requestRestartArguments;
private static ILifecycleService? _requestRestartService;
private static readonly object _ExitLock = new();
/// <summary>
/// WPF 应用程序容器,在 <see cref="LifecycleState.BeforeLoading"/> 阶段为空值
/// </summary>
public static Application CurrentApplication { get; set; } = null!;
/// <summary>
/// 是否正在关闭程序
/// </summary>
public static bool HasShutdownStarted { get; private set; } = false;
/// <summary>
/// 正在进行的关闭程序流程是否是强制关闭
/// </summary>
public static bool IsForceShutdown { get; private set; } = false;
private static void _FatalExit()
{
ForceShutdown(-1);
}
private static void _Exit(int statusCode = 0)
{
lock (_ExitLock)
{
if (HasShutdownStarted) return;
HasShutdownStarted = true;
}
// 结束 Running 计时
if (_countRunningStart is { } start)
{
var countSpan = DateTime.Now - start;
_LogStateCount(countSpan, LifecycleState.Running);
}
// 开始 Exiting 状态
_StartStateFlow(LifecycleState.Exiting, count: false);
// 停止服务
Context.Debug("正在停止运行中的服务");
ILifecycleLogService? logService = null;
while (_StartedServiceStack.TryPop(out var service))
{
// 跳过已标记为停止的服务
if (_RunningServiceInfoMap.TryGetValue(service.Identifier, out var info) && info.IsStopped) continue;
// 跳过日志服务
if (service is ILifecycleLogService ls)
{
Context.Trace($"已跳过日志服务: {_ServiceName(ls)}");
logService = ls;
continue;
}
// 执行停止流程
_StopService(service, service.SupportAsync);
}
_WaitStoppingServiceTasks();
if (logService is not null)
{
Context.Trace("退出过程已结束,正在停止日志服务");
// 直接调用 StopAsync() 不使用常规停止实现 以保证正常情况下不会向等待区输出日志
logService.StopAsync().ConfigureAwait(false).GetAwaiter().GetResult();
Console.WriteLine("[Lifecycle] Log service stopped");
}
_SavePendingLogs();
if (_hasRequestedRestart && _requestRestartService is { } s)
{
Console.WriteLine($"[Lifecycle] Requested by '{s.Identifier}', restarting the program...");
_RunCurrentExecutable(_requestRestartArguments);
}
// 退出程序
Console.WriteLine($"[Lifecycle] Exiting program with status: {statusCode}");
// 执行正常退出
if (statusCode == -1) Basics.CurrentProcess.Kill();
else Environment.Exit(statusCode);
// 保险起见,只要运行环境正常根本不可能执行到这里,但是永远都不能假设用户的环境是正常的
Console.WriteLine("[Lifecycle] Warning! Abnormal behaviour, try to kill process 1s later.");
Thread.Sleep(1000);
_KillCurrentProcess();
}
/// <summary>
/// 发起关闭程序流程。<br/>
/// <see cref="LifecycleState.BeforeLoading"/> 状态请使用 <see cref="LifecycleContext.RequestExit"/>。
/// </summary>
/// <param name="statusCode">退出状态码 (返回值)</param>
/// <param name="force">指定是否强制关闭,即不执行 WPF 标准关闭流程</param>
/// <exception cref="InvalidOperationException">尝试在 <see cref="LifecycleState.BeforeLoading"/> 状态调用</exception>
public static void Shutdown(int statusCode = 0, bool force = false)
{
if (CurrentState == LifecycleState.BeforeLoading) throw new InvalidOperationException();
if (HasShutdownStarted) return;
Context.Info(force ? "开始强制关闭程序" : "正在关闭程序");
IsForceShutdown = force;
if (force) _Exit(statusCode);
else new Thread(() => _Exit(statusCode)) { Name = "Lifecycle/Shutdown" }.Start();
}
/// <summary>
/// 强制关闭程序,不执行 WPF 标准关闭流程。
/// </summary>
/// <param name="statusCode">退出状态码 (返回值)</param>
/// <exception cref="InvalidOperationException">尝试在 <see cref="LifecycleState.BeforeLoading"/> 时调用</exception>
public static void ForceShutdown(int statusCode = 0) => Shutdown(statusCode, true);
}
@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using PCL.Core.Logging;
namespace PCL.Core.App.IoC;
partial class Lifecycle
{
private static ILifecycleLogService? _logService;
private static readonly List<LifecycleLogItem> _PendingLogs = [];
private static void _PushLog(LifecycleLogItem item, ILifecycleLogService service)
{
service.OnLog(item);
}
public static string PendingLogDirectory { get; set; } = @"PCL\Log";
public static string PendingLogFileName { get; set; } = "LastPending.log";
/// <summary>
/// 日志服务启动状态
/// </summary>
public static bool IsLogServiceStarted => _logService is not null;
private static void _SavePendingLogs()
{
if (_PendingLogs.Count == 0)
{
Console.WriteLine("[Lifecycle] No pending logs");
return;
}
try
{
// 直接写入剩余未输出日志到程序目录
var path = Path.Combine(PendingLogDirectory, PendingLogFileName);
if (!Path.IsPathRooted(path)) path = Path.Combine(Basics.ExecutableDirectory, path);
Directory.CreateDirectory(Basics.GetParentPathOrDefault(path));
using var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
using var writer = new StreamWriter(stream, Encoding.UTF8);
foreach (var item in _PendingLogs) writer.WriteLine(item.ComposeMessage());
Console.WriteLine($"[Lifecycle] Pending logs saved to {path}");
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.WriteLine("[Lifecycle] Error saving pending logs, writing to stdout...");
foreach (var item in _PendingLogs) Console.WriteLine(item.ComposeMessage());
}
}
}
/// <summary>
/// 生命周期日志项
/// </summary>
/// <param name="Source">日志来源</param>
/// <param name="Message">日志内容</param>
/// <param name="Exception">相关异常</param>
/// <param name="Level">日志等级</param>
/// <param name="ActionLevel">行为等级</param>
public readonly record struct LifecycleLogItem(
ILifecycleService? Source,
string Message,
Exception? Exception,
LogLevel Level,
ActionLevel ActionLevel)
{
/// <summary>
/// 创建该日志项的时间
/// </summary>
public DateTime Time { get; } = DateTime.Now;
/// <summary>
/// 创建该日志项的 Task ID 或线程名
/// </summary>
public string ContextName { get; } =
(Task.CurrentId is { } id ? $"TSK#{id}" : null)
?? Thread.CurrentThread.Name
?? $"#{Environment.CurrentManagedThreadId}";
public override string ToString()
{
var source = (Source is null) ? "" : $" [{Source.Name}|{Source.Identifier}]";
var basic = $"[{Time:HH:mm:ss.fff}]{source}";
return Exception is null ? $"{basic} {Message}" : $"{basic} ({Message}) {Exception.GetType().FullName}: {Exception.Message}";
}
public string ComposeMessage()
{
var source = (Source is null) ? "" : $" [{Source.Name}|{Source.Identifier}]";
var result = $"[{Time:HH:mm:ss.fff}] [{Level.RealLevel().PrintName()}] [{ContextName}]{source} {Message}";
if (Exception is not null) result += $"\n{Exception}";
return result;
}
}
/// <summary>
/// 日志服务专用接口。整个生命周期只能有一个日志服务,若出现第二个将会报错。
/// </summary>
public interface ILifecycleLogService : ILifecycleService
{
/// <summary>
/// 记录日志的事件
/// </summary>
public void OnLog(LifecycleLogItem item);
}
@@ -0,0 +1,26 @@
using System;
using System.Diagnostics;
namespace PCL.Core.App.IoC;
partial class Lifecycle
{
private static void _RunCurrentExecutable(string? arguments)
{
var fileName = Environment.ProcessPath!;
if (arguments is null) Process.Start(fileName);
else Process.Start(fileName, arguments);
}
private static void _KillCurrentProcess()
{
var psi = new ProcessStartInfo
{
FileName = "taskkill.exe",
Arguments = $"/f /t /pid {Environment.ProcessId}",
UseShellExecute = false,
CreateNoWindow = true
};
Process.Start(psi);
}
}
@@ -0,0 +1,402 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace PCL.Core.App.IoC;
partial class Lifecycle
{
private static readonly ConcurrentDictionary<string, LifecycleServiceInfo> _RunningServiceInfoMap = [];
private static readonly ConcurrentStack<ILifecycleService> _StartedServiceStack = [];
private static readonly Dictionary<string, ILifecycleService> _ManualServiceMap = [];
private static readonly HashSet<ILifecycleService> _DeclaredStoppedServices = [];
private static readonly ConcurrentDictionary<string, Exception> _ServiceLastExceptionMap = [];
/// <summary>
/// 所有正在运行的服务项标识符(即 <see cref="ILifecycleService.Identifier"/> 属性)
/// </summary>
public static ICollection<string> RunningServices => _RunningServiceInfoMap.Keys;
/// <summary>
/// 服务项启动前触发的事件。<br/>
/// 该事件会在与服务项初始化相同的线程中执行。请注意,该事件可能在多个不同线程中被同时调用。
/// </summary>
public static event Action<string>? ServiceStarting;
/// <summary>
/// 服务项启动后触发的事件。<br/>
/// 该事件会在与服务项初始化相同的线程中执行。请注意,该事件可能在多个不同线程中被同时调用。
/// </summary>
public static event Action<string>? ServiceStarted;
/// <summary>
/// 服务项停止前触发的事件。特别地,主动声明停止 (<see cref="LifecycleContext.DeclareStopped"/>) 的服务不会触发该事件。<br/>
/// 该事件会在与服务项停止相同的线程中执行。请注意,该事件可能在多个不同线程中被同时调用。
/// </summary>
public static event Action<string>? ServiceStopping;
/// <summary>
/// 服务项停止后触发的事件。特别地,主动声明停止 (<see cref="LifecycleContext.DeclareStopped"/>) 的服务不会触发该事件。<br/>
/// 该事件会在与服务项停止相同的线程中执行。请注意,该事件可能在多个不同线程中被同时调用。
/// </summary>
public static event Action<string>? ServiceStopped;
/// <summary>
/// 服务项主动声明停止 (<see cref="LifecycleContext.DeclareStopped"/>) 触发的事件。<br/>
/// 该事件会在与服务项初始化相同的线程中执行。请注意,该事件可能在多个不同线程中被同时调用。
/// </summary>
public static event Action<string>? ServiceDeclaredStopped;
/// <summary>
/// 服务项抛出异常时触发的事件。<br/>
/// 该事件会在服务抛出异常的线程中执行。请注意,该事件可能在多个不同线程中被同时调用。
/// </summary>
public static event Action<string, Exception>? ServiceUnhandledException;
private static string _ServiceName(ILifecycleService service, LifecycleState? state = null)
{
#if DEBUG
var info = GetServiceInfo(service.Identifier);
if (info is not null) state = info.StartState;
var stateText = (state is null) ? "" : $"{state}/";
return $"{service.Name} ({stateText}{service.Identifier})";
#else
return service.Name;
#endif
}
private static Task _StartServiceTask(ILifecycleService service, bool manual = false)
{
ILifecycleLogService? logService = null;
// 检测日志服务
if (service is ILifecycleLogService ls)
{
if (_logService is not null) throw new InvalidOperationException("日志服务只能有一个");
logService = ls;
}
var state = manual ? LifecycleState.Manual : CurrentState;
var name = _ServiceName(service, state);
// 确保不存在重复的标识符
lock (_ManualServiceMap) {
if (_ManualServiceMap.ContainsKey(service.Identifier) && IsServiceRunning(service.Identifier))
{
Context.Warn($"{name} 标识符重复,已跳过");
return Task.CompletedTask;
}
// 先找个东西占着防止异步加载中检测逻辑失效
_RunningServiceInfoMap[service.Identifier] = _SystemServiceInfo;
}
// 运行服务项并添加到正在运行列表
return service.SupportAsync ? Task.Run(AsyncCall) : AsyncCall();
async Task AsyncCall()
{
try
{
Context.Trace($"正在启动 {name}");
ServiceStarting?.Invoke(service.Identifier);
await service.StartAsync().ConfigureAwait(!service.SupportAsync);
var serviceInfo = new LifecycleServiceInfo(service, state);
Context.Debug($"{name} 启动成功");
if (_DeclaredStoppedServices.Contains(service))
{
_DeclaredStoppedServices.Remove(service);
ServiceDeclaredStopped?.Invoke(service.Identifier);
Context.Trace($"{name} 已中止");
}
else
{
// 若该服务未声明自己已结束运行,将其添加到正在运行列表
_StartedServiceStack.Push(service);
_RunningServiceInfoMap[service.Identifier] = serviceInfo;
ServiceStarted?.Invoke(service.Identifier);
}
}
catch (Exception ex)
{
Context.Warn($"{name} 启动失败,尝试停止", ex);
// 存储异常并停止服务
_UpdateLastException(service, ex);
_StopService(service, false);
}
// 若日志服务已启动则清空日志缓冲
if (logService is null) return;
lock (_PendingLogs)
{
foreach (var item in _PendingLogs) _PushLog(item, logService);
_PendingLogs.Clear();
_logService = logService;
}
}
}
private static Type[] _GetServiceTypes(LifecycleState state) => LifecycleServiceTypes.GetServiceTypes(state);
private static ILifecycleService _CreateService(Type type)
{
var fullname = type.FullName;
try
{
SystemContext.Trace($"正在实例化 {fullname}");
var instance = (ILifecycleService)Activator.CreateInstance(type, true)!;
var supportAsyncText = instance.SupportAsync ? "异步" : "同步";
SystemContext.Trace($"实例化完成: {instance.Name} ({instance.Identifier}), 启动方式: {supportAsyncText}");
return instance;
}
catch (Exception ex)
{
SystemContext.Fatal($"注册服务项实例化失败: {fullname}", ex);
throw;
}
}
private static void _LogStateCount(TimeSpan count, LifecycleState state)
{
Context.Debug($"状态 {state} 共用时 {Math.Round(count.TotalMilliseconds)} ms");
}
private static void _InitializeAndStartStateServices(LifecycleState state)
{
var types = _GetServiceTypes(state);
if (types.Length == 0) return; // 跳过空列表
var asyncInstances = new List<ILifecycleService>();
// 运行非异步启动服务并存储异步启动服务
foreach (var service in types)
{
var instance = _CreateService(service);
if (instance.SupportAsync) asyncInstances.Add(instance);
else _StartServiceTask(instance).ConfigureAwait(false).GetAwaiter().GetResult();
if (_hasRequestedStopLoading) return; // 若请求停止加载则提前结束
}
// 运行异步启动服务并等待所有服务启动完成
Task.WaitAll(asyncInstances.Select(instance => _StartServiceTask(instance)).ToArray());
}
private static void _StartStateFlow(LifecycleState start, LifecycleState? end = null, bool count = true)
{
var index = (int)start;
var endIndex = end is null ? index : (int)end;
while (index <= endIndex)
{
DateTime? countStart = count ? DateTime.Now : null; //开始计时
var state = (LifecycleState)index;
_NextState(state);
_InitializeAndStartStateServices(state);
if (countStart is { } s)
{
var countSpan = DateTime.Now - s; // 结束计时
_LogStateCount(countSpan, state);
}
index++;
}
}
private static void _StartWorker(LifecycleState state, LifecycleState? wait = null, bool count = true)
{
new Thread(() =>
{
_StartStateFlow(state, count: count);
if (wait is { } w) WaitForState(w);
})
{ IsBackground = true, Name = $"Lifecycle/{state}" }.Start();
}
private static void _RemoveRunningInstance(ILifecycleService service)
{
_RunningServiceInfoMap.TryRemove(service.Identifier, out var removed);
removed?.MarkAsStopped();
}
private static readonly ConcurrentBag<Task> _StoppingServiceTasks = [];
private static void _WaitStoppingServiceTasks()
{
Task.WaitAll(_StoppingServiceTasks.ToArray());
}
private static void _StopService(ILifecycleService service, bool async, bool manual = false)
{
var name = _ServiceName(service, manual ? LifecycleState.Manual : CurrentState);
if (async) _StoppingServiceTasks.Add(Task.Run(Stop));
else Stop().ConfigureAwait(false).GetAwaiter().GetResult();
return;
async Task Stop()
{
try
{
Context.Trace($"正在停止 {name}");
ServiceStopping?.Invoke(service.Identifier);
await service.StopAsync().ConfigureAwait(!async);
ServiceStopped?.Invoke(service.Identifier);
Context.Debug($"{name} 已停止");
}
catch (Exception ex)
{
// 若出错则存储异常并忽略
Context.Warn($"停止 {name} 时出错,已跳过", ex);
_UpdateLastException(service, ex);
}
// 从正在运行列表移除
_RemoveRunningInstance(service);
}
}
private static void _UpdateLastException(ILifecycleService service, Exception ex)
{
_ServiceLastExceptionMap[service.Identifier] = ex;
ServiceUnhandledException?.Invoke(service.Identifier, ex);
}
/// <summary>
/// 检查指定标识符的服务项是否正在运行
/// </summary>
/// <param name="identifier">服务项标识符(即 <see cref="ILifecycleService.Identifier"/> 属性)</param>
/// <returns>服务项是否正在运行</returns>
public static bool IsServiceRunning(string identifier) => _RunningServiceInfoMap.ContainsKey(identifier);
/// <summary>
/// 根据标识符获取正在运行的服务项的相关信息
/// </summary>
/// <param name="identifier">服务项标识符(即 <see cref="ILifecycleService.Identifier"/> 属性)</param>
/// <returns>服务项信息</returns>
public static LifecycleServiceInfo? GetServiceInfo(string? identifier)
{
if (identifier is null) return null;
_RunningServiceInfoMap.TryGetValue(identifier, out var info);
return info;
}
/// <summary>
/// 获取服务项初始化或结束逻辑的最后一次异常。
/// </summary>
/// <param name="identifier">服务项标识符(即 <see cref="ILifecycleService.Identifier"/> 属性)</param>
/// <returns>异常实例,若未出现异常则为 <c>null</c></returns>
public static Exception? GetServiceLastException(string identifier)
{
var result = _ServiceLastExceptionMap.TryGetValue(identifier, out var ex);
return result ? ex : null;
}
/// <summary>
/// 手动请求启动一个周期为 <see cref="LifecycleState.Manual"/> 的服务项。
/// </summary>
/// <param name="identifier">服务项标识符(即 <see cref="ILifecycleService.Identifier"/> 属性)</param>
/// <param name="async">是否异步启动,默认遵循服务项自身的声明</param>
/// <returns>是否成功请求启动,若该服务项正在运行或周期不是 <see cref="LifecycleState.Manual"/> 则无法启动</returns>
public static bool StartService(string identifier, bool? async = null)
{
_ManualServiceMap.TryGetValue(identifier, out var service);
if (service is null || IsServiceRunning(identifier)) return false;
async ??= service.SupportAsync;
if (async == true) Task.Run(() => _StartServiceTask(service, true));
else _StartServiceTask(service, true);
return true;
}
/// <summary>
/// 手动请求停止一个周期为 <see cref="LifecycleState.Manual"/> 的服务项。
/// </summary>
/// <param name="identifier">服务项标识符(即 <see cref="ILifecycleService.Identifier"/> 属性)</param>
/// <param name="async">是否异步停止,默认为 <c>true</c></param>
/// <returns>是否成功请求停止,若该服务项未运行或周期不是 <see cref="LifecycleState.Manual"/> 则无法停止</returns>
public static bool StopService(string identifier, bool async = true)
{
_ManualServiceMap.TryGetValue(identifier, out var service);
if (service is null || !IsServiceRunning(identifier)) return false;
_StopService(service, async, true);
return true;
}
/// <summary>
/// 运行自定义服务项。该服务项将使用当前生命周期状态作为启动状态,若无特殊需求请尽可能不要使用,而是直接注册服务项。
/// </summary>
/// <param name="service">服务项实例</param>
/// <returns>是否成功请求运行,若标识符与正在运行的服务或已注册的手动服务冲突则无法运行。</returns>
public static bool StartCustomService(ILifecycleService service)
{
if (IsServiceRunning(service.Identifier) || _ManualServiceMap.ContainsKey(service.Identifier)) return false;
_StartServiceTask(service);
return true;
}
}
/// <summary>
/// 用于特定生命周期的服务模型。<br/>
/// 实现特殊的子接口 <see cref="ILifecycleLogService"/> 以声明自己是日志服务。
/// </summary>
public interface ILifecycleService
{
/// <summary>
/// 全局唯一标识符,统一使用纯小写字母与 “-” 的命名格式,如 <c>logger</c> <c>yggdrasil-server</c> 等。
/// </summary>
public string Identifier { get; }
/// <summary>
/// 友好名称,如 “日志” “验证服务端” 等,将会用于记录日志等场合。
/// </summary>
public string Name { get; }
/// <summary>
/// 声明该服务是否支持异步启动。
/// 每个生命周期均会依次同步启动不支持异步启动的服务,然后依次异步启动支持异步启动的服务,启动的执行顺序遵循声明的优先级。<br/>
/// 支持异步启动对启动器整体启动速度有一定帮助,在允许的情况下应尽最大可能支持。
/// </summary>
public bool SupportAsync { get; }
/// <summary>
/// 启动该服务。应由生命周期管理自动调用,若无特殊情况,请勿手动调用。
/// </summary>
public Task StartAsync();
/// <summary>
/// 停止该服务。应由生命周期管理自动调用,若无特殊情况,请勿手动调用。
/// </summary>
public Task StopAsync();
}
/// <summary>
/// 生命周期服务项的信息记录
/// </summary>
public record LifecycleServiceInfo
{
private readonly ILifecycleService _service;
public string Identifier => _service.Identifier;
public string Name => _service.Name;
public bool CanStartAsync => _service.SupportAsync;
public LifecycleState StartState { get; }
/// <summary>
/// 服务开始运行的时间。初始值为调用 <c>Start()</c> 方法的时刻,在 <c>Start()</c> 方法结束之后会更新一次。
/// </summary>
public DateTime StartTime { get; init; } = DateTime.Now;
/// <summary>
/// 附带启动状态的完整标识符。
/// </summary>
public string FullIdentifier => $"{StartState}/{Identifier}";
/// <summary>
/// 服务是否正常运行,若已停止则该值为 <c>false</c>,否则为 <c>true</c>。
/// </summary>
public bool IsStopped { get; private set; } = false;
/// <summary>
/// 将该服务标记为已停止,将不会在程序退出流程中调用该服务的 <c>Stop()</c> 方法。
/// </summary>
public void MarkAsStopped() => IsStopped = true;
/// <summary>
/// 本 record 应由生命周期管理自动构造,若无特殊情况,请勿手动调用。
/// </summary>
/// <param name="service">生命周期服务项实例</param>
/// <param name="startState">启动的生命周期状态</param>
public LifecycleServiceInfo(ILifecycleService service, LifecycleState startState)
{
_service = service;
StartState = startState;
StartTime = DateTime.Now;
}
}
@@ -0,0 +1,177 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace PCL.Core.App.IoC;
partial class Lifecycle
{
/// <summary>
/// 生命周期状态改变时触发的事件。<br/>
/// <b>非异步执行,请注意自行实现必要的异步,否则会卡住生命周期管理线程。</b>
/// </summary>
public static event Action<LifecycleState>? StateChanged;
/// <summary>
/// 当前的生命周期状态,会随生命周期变化随时更新。
/// </summary>
public static LifecycleState CurrentState
{
get;
private set
{
Context.Debug($"状态改变: {value}");
field = value;
try
{
StateChanged?.Invoke(value);
}
catch (Exception ex)
{
Context.Warn("状态更改事件出错", ex);
}
}
} = LifecycleState.BeforeLoading;
private static void _NextState(LifecycleState? enforce = null)
{
if (enforce is { } state) CurrentState = state;
else CurrentState++;
}
/// <summary>
/// 阻塞当前线程并等待到达指定生命周期状态。
/// </summary>
/// <param name="state">指定生命周期状态</param>
/// <returns>
/// 是否真正“等待”过(若调用该方法时已经到达或晚于指定状态,则为 <c>false</c>
/// </returns>
public static bool WaitForState(LifecycleState state)
{
if (CurrentState >= state) return false; // 如果已经是目标状态,直接返回
using var mre = new ManualResetEventSlim(false);
StateChanged += TempHandler;
try { mre.Wait(); } // 等待 Set() 方法
finally { StateChanged -= TempHandler; } // 取消订阅,避免内存泄漏或重复唤醒
return true;
void TempHandler(LifecycleState s)
{
// ReSharper disable once AccessToDisposedClosure
if (s == state) mre.Set();
}
}
/// <summary>
/// 异步等待到达指定生命周期状态。
/// </summary>
/// <param name="state">指定生命周期状态</param>
/// <returns>
/// 结果表示是否真正“等待”过的 <see cref="Task"/> 实例(若调用该方法时已经到达或晚于指定状态,则结果为 <c>false</c>
/// </returns>
public static Task<bool> WaitForStateAsync(LifecycleState state)
{
if (CurrentState >= state) return Task.FromResult(false); // 如果已经是目标状态,则直接返回 false
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
StateChanged += TempHandler;
return tcs.Task;
void TempHandler(LifecycleState s)
{
if (s != state) return;
StateChanged -= TempHandler;
tcs.TrySetResult(true);
}
}
/// <summary>
/// 快速注册改变到目标生命周期状态的事件,与直接注册 <see cref="StateChanged"/> 的区别是会自动判断目标状态并自动移除事件注册。
/// </summary>
/// <param name="when">目标生命周期状态</param>
/// <param name="action">事件触发委托</param>
public static void When(LifecycleState when, Action action)
{
if (CurrentState >= when) return;
StateChanged += TempHandler;
return;
void TempHandler(LifecycleState state)
{
if (state != when) return;
action();
StateChanged -= TempHandler;
}
}
}
/// <summary>
/// 生命周期状态
/// </summary>
public enum LifecycleState
{
/// <summary>
/// <b>手动运行</b><br/>
/// 表示不应由生命周期管理自动运行。拥有该状态的服务项可以使用 <see cref="Lifecycle.StartService"/>
/// 和 <see cref="Lifecycle.StopService"/> 手动控制启动和停止。<br/>
/// 非异步启动可能在任意线程执行。
/// </summary>
Manual,
/// <summary>
/// <b>预加载</b><br/>
/// 一些提前运行的无需使用基本组件的事件,如检测单例、提权进程、更新等。
/// 在该状态运行的服务可以使用 <see cref="LifecycleContext.RequestExit"/> 请求直接退出程序。<br/>
/// 非异步启动将在 STA 线程执行。
/// </summary>
BeforeLoading,
/// <summary>
/// <b>加载</b><br/>
/// 基本组件初始化,如日志、系统基本信息、设置项等。<br/>
/// 非异步启动将在 STA 线程执行。
/// </summary>
Loading,
/// <summary>
/// <b>加载结束</b><br/>
/// 非基本组件初始化,大多数功能性组件如 RPC 服务端、Yggdrasil 服务端的初始化等,均应在此时运行。<br/>
/// 非异步启动将在 STA 线程执行,不建议在此状态非异步启动。
/// </summary>
Loaded,
/// <summary>
/// <b>窗口创建</b><br/>
/// 主窗体内容初始化,正常情况下不应有任何与主窗体初始化无关的事件在此时运行。<br/>
/// 非异步启动将在 STA 线程执行。
/// </summary>
WindowCreating,
/// <summary>
/// <b>窗口创建结束</b><br/>
/// 一些事件需要依赖已经加载完成的窗体,如初始弹窗提示、主题刷新等,应在此时运行。<br/>
/// 非异步启动将在 STA 线程执行,耗时操作可能导致主窗体卡顿。
/// </summary>
WindowCreated,
/// <summary>
/// <b>正在运行</b><br/>
/// 程序开始正常运行后的工作,如检查更新。<br/>
/// 非异步启动将在新的工作线程执行。
/// </summary>
Running,
/// <summary>
/// <b>尝试关闭程序</b><br/>
/// 可能有服务需要阻止启动器退出?类似 WPF 窗体的 Closing 事件,但启动器应该没这需求吧...<br/>
/// 非异步启动将在 STA 线程执行,耗时操作可能导致主窗体卡顿。
/// </summary>
Closing,
/// <summary>
/// <b>关闭程序</b><br/>
/// 确认关闭程序后开始执行关闭流程,一些需要保存状态的服务项应在此时运行。
/// 生命周期管理会在此时自动执行所有托管的未停止服务项的 <c>Stop</c> 方法,因此托管的服务项无需额外关注该状态。<br/>
/// 非异步启动可能在任意线程执行,耗时操作可能导致主窗体卡顿。
/// </summary>
Exiting,
}
@@ -0,0 +1,17 @@
using System;
namespace PCL.Core.App.IoC;
public class PropertyAccessor<TProperty>(Func<TProperty>? getter = null, Action<TProperty>? setter = null)
{
public TProperty Value
{
get => (getter ?? throw new NotSupportedException("Write-only property"))();
set => (setter ?? throw new NotSupportedException("Read-only property"))(value);
}
public bool CanGet => getter is not null;
public bool CanSet => setter is not null;
public bool IsReadOnly => setter is null;
public bool IsWriteOnly => getter is null;
}
@@ -0,0 +1,290 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using Humanizer;
using PCL.Core.App.IoC;
using PCL.Core.IO;
namespace PCL.Core.App.Localization;
/// <summary>
/// <p>
/// 本地化文本与展示格式访问辅助。
/// </p>
/// <p>
/// <see cref="Lang" /> 用于代码中读取本地化文本资源,以及按照当前展示区域性格式化文本参数、日期时间和数值。
/// 它面向 C# 代码侧调用;XAML 中的静态文本优先使用 <c>DynamicResource</c>
/// XAML 绑定值的格式化优先使用 <see cref="LocalizationFormatConverter" />。
/// </p>
/// <p>
/// 文本资源来自当前应用的资源字典。正常运行时优先通过
/// <see cref="Application.Current" /> 查找资源;在应用生命周期早期或测试环境中,
/// 会回退到 <see cref="Lifecycle.CurrentApplication" /> 进行一次安全查找。
/// </p>
/// <p>
/// 该类中的格式化方法使用 <see cref="CultureInfo.CurrentCulture" />
/// 因此会跟随 <see cref="LocalizationService" /> 当前设置的展示格式区域性。
/// 它们只适合生成展示给用户看的文本,不应用于配置文件、日志、协议、缓存键、文件名等
/// 需要稳定格式的场景;这些场景应显式使用 <see cref="CultureInfo.InvariantCulture" />。
/// </p>
/// </summary>
public static class Lang
{
private static readonly HashSet<string> _UnrestrictedTimeZones = new(StringComparer.OrdinalIgnoreCase)
{
"China Standard Time"
};
private static readonly HashSet<string> _UnrestrictedCultures = new(StringComparer.OrdinalIgnoreCase)
{
"zh-CN"
};
/// <summary>
/// 当前代码侧本地化格式化使用的展示区域性。
/// </summary>
public static CultureInfo Culture { get; private set; } = CultureInfo.CurrentCulture;
/// <summary>
/// 当前功能是否不受限制。
/// </summary>
public static bool IsFeaturesUnrestricted =>
Config.Debug.AllowRestrictedFeature ||
(
_UnrestrictedTimeZones.Contains(TimeZoneInfo.Local.Id) &&
(_UnrestrictedCultures.Contains(CultureInfo.CurrentCulture.Name) ||
_UnrestrictedCultures.Contains(CultureInfo.CurrentUICulture.Name))
);
/// <summary>
/// 当前展示区域性 <see cref="Culture"/> 是否为 <c>zh-CN</c>。
/// </summary>
public static bool IsChineseMainland =>
Culture.Name == "zh-CN";
/// <summary>
/// 同步展示区域性。该方法由 <see cref="LocalizationService" /> 在语言或展示格式变化时调用。
/// </summary>
/// <param name="culture">
/// 新的展示区域性。
/// </param>
internal static void SyncCulture(CultureInfo culture)
{
Culture = culture;
}
/// <summary>
/// 获取指定资源键对应的本地化文本。
/// </summary>
/// <param name="key">
/// 资源键。不能为空、空字符串或空白字符串。
/// </param>
/// <returns>
/// 找到资源时返回本地化文本;
/// 未找到资源时,调试构建返回 <c>!key!</c>,发布构建返回 <paramref name="key" /> 本身。
/// </returns>
/// <exception cref="ArgumentException">
/// <paramref name="key" /> 为空、空字符串或空白字符串。
/// </exception>
public static string Text(string key)
{
ArgumentException.ThrowIfNullOrWhiteSpace(key);
if (Application.Current?.TryFindResource(key) is string text) return text;
if (_LifecycleSafeFindResource(key) is string fallbackText) return fallbackText;
#if DEBUG
return $"!{key}!";
#else
return key;
#endif
}
/// <summary>
/// 获取指定资源键对应的本地化格式文本,并使用当前展示区域性格式化参数。
/// 资源文本应使用标准 .NET 复合格式字符串,例如 <c>{0}</c>、<c>{1:N2}</c>。
/// 该方法适合代码中生成用户可见句子,例如提示、说明、状态文本。
/// </summary>
/// <param name="key">
/// 资源键。不能为空、空字符串或空白字符串。
/// </param>
/// <param name="args">
/// 用于填充资源文本中格式占位符的参数。
/// </param>
/// <returns>
/// 使用当前展示区域性格式化后的本地化文本。
/// </returns>
public static string Text(string key, params object?[] args)
{
return string.Format(Culture, Text(key), args);
}
/// <summary>
/// <p>使用当前展示区域性格式化日期时间。</p>
/// </summary>
/// <param name="value">
/// 要格式化的日期时间。
/// </param>
/// <param name="format">
/// 标准或自定义日期时间格式字符串,默认使用 <c>G</c>。
/// </param>
/// <returns>
/// 使用当前展示区域性格式化后的日期时间文本。
/// </returns>
public static string Date(DateTime value, string format = "G")
{
return value.ToString(format, Culture);
}
/// <summary>
/// <p>使用当前展示区域性格式化日期时间。</p>
/// </summary>
/// <param name="value">
/// 要格式化的日期时间。
/// </param>
/// <param name="format">
/// 标准或自定义日期时间格式字符串,默认使用 <c>G</c>。
/// </param>
/// <returns>
/// 使用当前展示区域性格式化后的日期时间文本。
/// </returns>
public static string Date(DateTimeOffset value, string format = "G")
{
return value.ToString(format, Culture);
}
/// <summary>
/// 使用当前展示区域性格式化时间间隔。
/// </summary>
/// <param name="span">
/// 要格式化的时间间隔。负值表示过去,正值表示未来。
/// </param>
/// <param name="precision">
/// 要显示的时间单位数量。例如值为 <c>1</c> 时仅显示最大的一项时间单位,值为 <c>2</c> 时显示两项时间单位。
/// </param>
/// <param name="addAffixes">
/// 如果为 <see langword="true" />,会根据 <paramref name="span" /> 的正负为文本添加“过去”或“未来”语义;
/// 如果为 <see langword="false" />,仅返回格式化后的时间间隔本身。
/// </param>
/// <param name="maxUnit">
/// 允许显示的最大时间单位。
/// </param>
/// <param name="minUnit">
/// 允许显示的最小时间单位。
/// </param>
/// <returns>
/// 使用当前展示区域性格式化后的时间间隔文本;当 <paramref name="addAffixes" /> 为 <see langword="true" /> 时,
/// 返回值会包含相对于当前时间的过去或未来语义。
/// </returns>
public static string TimeSpan(
TimeSpan span, int precision = 2, bool addAffixes = true,
TimeUnit maxUnit = TimeUnit.Year, TimeUnit minUnit = TimeUnit.Hour)
{
var isPast = span.TotalMilliseconds < 0;
if (isPast) span = span.Negate();
var text = span.Humanize(
precision,
maxUnit: maxUnit,
minUnit: minUnit,
culture: Culture);
return addAffixes
? Text(isPast ? "Common.Format.TimeSpan.Past" : "Common.Format.TimeSpan.Future", text)
: text;
}
/// <summary>
/// 使用当前展示区域性格式化数值。
/// </summary>
/// <typeparam name="T">
/// 实现 <see cref="IFormattable" /> 的数值或可格式化类型。
/// </typeparam>
/// <param name="value">
/// 要格式化的值。
/// </param>
/// <param name="format">
/// 标准或自定义格式字符串。为 <see langword="null" /> 时使用类型默认格式。
/// </param>
/// <returns>
/// 使用当前展示区域性格式化后的文本。
/// </returns>
public static string Number<T>(T value, string? format = null) where T : IFormattable
{
return value.ToString(format, Culture);
}
/// <summary>
/// 使用当前展示区域性格式化紧凑数值,例如下载量。
/// </summary>
/// <param name="value">
/// 要格式化的整数。
/// </param>
/// <returns>
/// 按当前展示区域性格式化后的紧凑数值文本,例如 <c>11M</c>、<c>2 万</c>。
/// </returns>
public static string CompactNumber(long value)
{
var absValue = Math.Abs((double)value);
var sign = value < 0 ? -1 : 1;
// 使用 4 位进位
if (_IsEastAsianCulture(Culture.Name))
return absValue switch
{
> 1_000_000_000_000d => Text("Common.Format.Number.Digit3",
Number(sign * absValue / 1_000_000_000_000d, "N2")),
> 100_000_000d => Text("Common.Format.Number.Digit2",
Number(sign * absValue / 100_000_000d, "N2")),
> 100_000d => Text("Common.Format.Number.Digit1",
Number(sign * Math.Round(absValue / 10_000d), "N0")),
_ => Number(value, "N0")
};
// 使用 3 位进位
return absValue switch
{
> 1_000_000_000d => Text("Common.Format.Number.Digit3",
Number(sign * absValue / 1_000_000_000d, "N2")),
> 1_000_000d => Text("Common.Format.Number.Digit2",
Number(sign * absValue / 1_000_000d, "N2")),
> 10_000d => Text("Common.Format.Number.Digit1",
Number(sign * Math.Round(absValue / 1_000d), "N0")),
_ => Number(value, "N0")
};
}
/// <summary>
/// 使用当前展示区域性格式化文件大小,例如 <c>1.28 MB</c>。
/// </summary>
/// <param name="length">字节数</param>
/// <param name="startUnit">起始单位索引,0 为 B</param>
/// <returns>格式化后的文件大小文本。</returns>
public static string FileSize(long length, int startUnit = 0)
{
return ByteStream.GetReadableLength(length, startUnit, Culture);
}
private static bool _IsEastAsianCulture(string cultureName)
{
return cultureName.StartsWith("zh", StringComparison.OrdinalIgnoreCase) ||
cultureName is "ja-JP" or "ko-KR" or "lzh";
}
private static object? _LifecycleSafeFindResource(string key)
{
try
{
return Lifecycle.CurrentApplication.TryFindResource(key);
}
catch (InvalidOperationException)
{
return null;
}
catch (NullReferenceException)
{
return null;
}
}
}
@@ -0,0 +1,13 @@
<!-- Formatted by xaml-lang-formatter at 2026-06-28T17:16:52. -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xml:space="preserve">
<!-- Meta -->
<sys:String x:Key="Meta.Code">en-GB</sys:String>
<sys:String x:Key="Meta.Name">English (United Kingdom)</sys:String>
</ResourceDictionary>
@@ -0,0 +1,3573 @@
<!-- Formatted by xaml-lang-formatter at 2026-06-28T17:16:52. -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xml:space="preserve">
<!-- Meta -->
<sys:String x:Key="Meta.Code">en-US</sys:String>
<sys:String x:Key="Meta.Name">English (US)</sys:String>
<!-- Application -->
<sys:String x:Key="Application.Animation.Error.OperationFailed">An animation operation failed. See the details below.</sys:String>
<sys:String x:Key="Application.Control.Error.OperationFailed">A user interface control operation failed. See the details below.</sys:String>
<sys:String x:Key="Application.Control.Loading.Default">Loading...</sys:String>
<sys:String x:Key="Application.Control.Loading.Failed">Loading failed</sys:String>
<sys:String x:Key="Application.Control.Loading.NetworkUnstable">Network conditions are poor. Please try again later.</sys:String>
<sys:String x:Key="Application.Control.Loading.UnknownError">Unknown error</sys:String>
<sys:String x:Key="Application.Control.MessageBox.Error.OperationFailed">A message dialog operation failed. See the details below.</sys:String>
<sys:String x:Key="Application.EnvironmentWarning.IKnow">I see</sys:String>
<sys:String x:Key="Application.EnvironmentWarning.Message">PCL CE detected environment issues on startup:&#xA;&#xA;{0}&#xA;&#xA;Some features may not work properly if these issues are not resolved...</sys:String>
<sys:String x:Key="Application.EnvironmentWarning.SocialSoftwareFolder">- PCL CE is running from a download folder of social apps such as QQ, WeChat, or TIM. Consider moving it elsewhere, otherwise game saves or settings may be lost.</sys:String>
<sys:String x:Key="Application.EnvironmentWarning.System32Bit">- The current system is 32-bit. PCL CE and newer versions of Minecraft no longer support it. It is strongly recommended to reinstall a 64-bit system before playing.</sys:String>
<sys:String x:Key="Application.EnvironmentWarning.TempFolder">- PCL CE is running from a temporary folder. Please extract PCL CE from the compressed file before using it, otherwise game saves or settings may be lost.</sys:String>
<sys:String x:Key="Application.EnvironmentWarning.Title">Environment warning</sys:String>
<sys:String x:Key="Application.EnvironmentWarning.WindowsVersion">- The Windows version does not meet the recommended requirement. Windows 10 1809 or later is recommended. Consider upgrading Windows.</sys:String>
<sys:String x:Key="Application.InitializationError.Path">PCL CE path: {0}</sys:String>
<sys:String x:Key="Application.InitializationError.PathUnavailable">Unable to obtain</sys:String>
<sys:String x:Key="Application.InitializationError.Title">PCL CE initialization error</sys:String>
<sys:String x:Key="Application.Loader.Error.OperationFailed">A task loading operation failed. See the details below.</sys:String>
<!-- Common -->
<sys:String x:Key="Common.Dialog.Title">Hint</sys:String>
<sys:String x:Key="Common.Dialog.Warning">Warning</sys:String>
<sys:String x:Key="Common.Error.InvalidJson">Not a valid JSON file</sys:String>
<sys:String x:Key="Common.Format.DownloadCount">{0} downloads</sys:String>
<sys:String x:Key="Common.Format.Number.Digit1">{0}k</sys:String>
<sys:String x:Key="Common.Format.Number.Digit2">{0}M</sys:String>
<sys:String x:Key="Common.Format.Number.Digit3">{0}B</sys:String>
<sys:String x:Key="Common.Format.TimeSpan.Future">in {0}</sys:String>
<sys:String x:Key="Common.Format.TimeSpan.Past">{0} ago</sys:String>
<sys:String x:Key="Common.Hint.Copied">Copied!</sys:String>
<sys:String x:Key="Common.Hint.CopyFailed">Failed to copy the text. The clipboard may be in use by another program.</sys:String>
<sys:String x:Key="Common.Hint.DoNotShowAgain">Do not show again</sys:String>
<sys:String x:Key="Common.Hint.FilesPasted">Pasted {0} files and {1} folders.</sys:String>
<sys:String x:Key="Common.Option.All">All</sys:String>
<sys:String x:Key="Common.Option.Auto">Auto</sys:String>
<sys:String x:Key="Common.Option.Customize">Customize</sys:String>
<sys:String x:Key="Common.Option.Default">Default</sys:String>
<sys:String x:Key="Common.Option.IDontKnow">I don't know</sys:String>
<sys:String x:Key="Common.Option.No">No</sys:String>
<sys:String x:Key="Common.Option.Yes">Yes</sys:String>
<sys:String x:Key="Common.State.Loading">Loading...</sys:String>
<sys:String x:Key="Common.State.Unknown">Unknown</sys:String>
<sys:String x:Key="Common.Status.Cancelled">Canceled!</sys:String>
<sys:String x:Key="Common.Status.Failure">Failure:</sys:String>
<sys:String x:Key="Common.Status.Success">Success!</sys:String>
<!-- Common.Action -->
<sys:String x:Key="Common.Action.Agree">Agree</sys:String>
<sys:String x:Key="Common.Action.Back">Back</sys:String>
<sys:String x:Key="Common.Action.Cancel">Cancel</sys:String>
<sys:String x:Key="Common.Action.Close">Close</sys:String>
<sys:String x:Key="Common.Action.Confirm">OK</sys:String>
<sys:String x:Key="Common.Action.Continue">Continue</sys:String>
<sys:String x:Key="Common.Action.Copy">Copy</sys:String>
<sys:String x:Key="Common.Action.Decline">Decline</sys:String>
<sys:String x:Key="Common.Action.Delete">Delete</sys:String>
<sys:String x:Key="Common.Action.Download">Download</sys:String>
<sys:String x:Key="Common.Action.GotIt">Got it</sys:String>
<sys:String x:Key="Common.Action.Initialize">Initialize</sys:String>
<sys:String x:Key="Common.Action.Install">Install</sys:String>
<sys:String x:Key="Common.Action.Open">Open</sys:String>
<sys:String x:Key="Common.Action.OpenFolder">Open folder</sys:String>
<sys:String x:Key="Common.Action.Overwrite">Override</sys:String>
<sys:String x:Key="Common.Action.Refresh">Refresh</sys:String>
<sys:String x:Key="Common.Action.Reset">Reset</sys:String>
<sys:String x:Key="Common.Action.Search">Search</sys:String>
<sys:String x:Key="Common.Action.UpdateLog">Changelog</sys:String>
<!-- Common.Installation -->
<sys:String x:Key="Common.Installation.Cleanroom">Cleanroom</sys:String>
<sys:String x:Key="Common.Installation.Fabric">Fabric</sys:String>
<sys:String x:Key="Common.Installation.FabricApi">Fabric API</sys:String>
<sys:String x:Key="Common.Installation.Forge">Forge</sys:String>
<sys:String x:Key="Common.Installation.Iris">Iris</sys:String>
<sys:String x:Key="Common.Installation.LabyMod">LabyMod</sys:String>
<sys:String x:Key="Common.Installation.LegacyFabric">Legacy Fabric</sys:String>
<sys:String x:Key="Common.Installation.LegacyFabricApi">Legacy Fabric API</sys:String>
<sys:String x:Key="Common.Installation.LiteLoader">LiteLoader</sys:String>
<sys:String x:Key="Common.Installation.Minecraft">Minecraft</sys:String>
<sys:String x:Key="Common.Installation.NeoForge">NeoForge</sys:String>
<sys:String x:Key="Common.Installation.OptiFabric">OptiFabric</sys:String>
<sys:String x:Key="Common.Installation.OptiFine">OptiFine</sys:String>
<sys:String x:Key="Common.Installation.Quilt">Quilt</sys:String>
<!-- Config -->
<sys:String x:Key="Config.Error.InvalidFormat.RecoveryMessage">Configuration initialization failed during the {0} stage.&#xA;&#xA;The configuration file at {1} contains invalid data and was backed up to {2}.&#xA;To help prevent the problem from happening again, PCL CE tried to restore a previous version when one was available. If you changed the configuration file manually, fix the problem and replace the current file.&#xA;&#xA;If you are not sure what happened, you can ignore this message for now. After restarting PCL CE, the affected settings may return to their defaults, but normal use should not be affected.</sys:String>
<sys:String x:Key="Config.Error.LoadFailed.DebugMessage">Configuration initialization failed during the {0} stage.&#xA;&#xA;The configuration system could not be initialized. Fix the problem and restart PCL CE.</sys:String>
<sys:String x:Key="Config.Error.LoadFailed.Message">The configuration system failed during the {0} stage.</sys:String>
<sys:String x:Key="Config.Error.LoadFailed.Title">Configuration initialization failed</sys:String>
<sys:String x:Key="Config.Error.SaveFailed.Message">Could not save the configuration file.&#xA;&#xA;Path: {0}</sys:String>
<sys:String x:Key="Config.Error.SaveFailed.Title">Failed to save configuration file</sys:String>
<!-- Crash -->
<sys:String x:Key="Crash.Analysis.Error.Failed">Crash analysis failed. See the details below.</sys:String>
<sys:String x:Key="Crash.Dialog.Button.ExportReport">Export crash report</sys:String>
<sys:String x:Key="Crash.Dialog.Button.GoToModify">Modify settings</sys:String>
<sys:String x:Key="Crash.Dialog.Button.OpenLog">View log</sys:String>
<sys:String x:Key="Crash.Dialog.Title.Auto">Minecraft crashed</sys:String>
<sys:String x:Key="Crash.Dialog.Title.Manual">Crash report analysis result</sys:String>
<sys:String x:Key="Crash.Export.Failed.CopyDetails">Copy details</sys:String>
<sys:String x:Key="Crash.Export.Failed.Message">The crash report could not be exported.&#xA;&#xA;Target path: {0}</sys:String>
<sys:String x:Key="Crash.Export.Failed.MessageWithoutPath">The crash report could not be exported, and the target path is unavailable.</sys:String>
<sys:String x:Key="Crash.Export.Failed.Title">Failed to export crash report</sys:String>
<sys:String x:Key="Crash.Presentation.ListItem"> - {0}</sys:String>
<sys:String x:Key="Crash.Presentation.ReasonOnly">Cause&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Presentation.WithFollowUps">{0}&#xA;&#xA;{1}</sys:String>
<sys:String x:Key="Crash.Presentation.WithSuggestion">Cause&#xA;{0}&#xA;&#xA;What you can try&#xA;{1}</sys:String>
<!-- Crash.Reason -->
<sys:String x:Key="Crash.Reason.ConfirmedMod.Multiple">The following mods caused the error:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.ConfirmedMod.Single">The mod {0} caused the error.</sys:String>
<sys:String x:Key="Crash.Reason.DuplicateMods.Known">Duplicate mods were detected:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.DuplicateMods.Unknown">Duplicate mods may be installed.</sys:String>
<sys:String x:Key="Crash.Reason.ExtractedModFile">An extracted mod file was detected.</sys:String>
<sys:String x:Key="Crash.Reason.FabricError.Generic">Fabric may have provided an error message.</sys:String>
<sys:String x:Key="Crash.Reason.FabricError.WithDetail">Fabric provided the following error message:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.FabricSolution.Generic">Fabric may have provided a solution.</sys:String>
<sys:String x:Key="Crash.Reason.FabricSolution.WithDetail">Fabric provided the following solution:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.FileOrContentValidationFailed">Some files or content did not pass validation.</sys:String>
<sys:String x:Key="Crash.Reason.ForgeError.Generic">Forge may have provided an error message.</sys:String>
<sys:String x:Key="Crash.Reason.ForgeError.WithDetail">Forge provided the following error message:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.GraphicsDriver.Generic">The graphics driver may be preventing the game from running correctly.</sys:String>
<sys:String x:Key="Crash.Reason.GraphicsDriver.IntelOrIntegrated">There may be a graphics driver problem, or the game may not be using the dedicated GPU.</sys:String>
<sys:String x:Key="Crash.Reason.IncompatibleMods.Generic">The installed mods may be incompatible.</sys:String>
<sys:String x:Key="Crash.Reason.IncompatibleMods.WithDetail">The installed mods are incompatible:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.IncompleteForgeInstallation">The Forge installation is incomplete and required files are missing.</sys:String>
<sys:String x:Key="Crash.Reason.InvalidModFileName">A mod file name contains special characters that Java cannot use.</sys:String>
<sys:String x:Key="Crash.Reason.JavaIncompatible">The selected Java version is incompatible with the game or mods.</sys:String>
<sys:String x:Key="Crash.Reason.JavaTooNew">The selected Java version is too new for this game or loader.</sys:String>
<sys:String x:Key="Crash.Reason.ManualDebugCrash">This was a manually triggered debug crash. The game itself is usually fine.</sys:String>
<sys:String x:Key="Crash.Reason.MissingDependency.Generic">Some mods are missing dependencies or do not match the current Minecraft version.</sys:String>
<sys:String x:Key="Crash.Reason.MissingDependency.WithDetail">Some mods are missing dependencies or do not match the current Minecraft version:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.MissingMixinBootstrap">MixinBootstrap is missing, so the game cannot continue loading.</sys:String>
<sys:String x:Key="Crash.Reason.ModConfigCrash.Simple">The configuration for the mod {0} could not be read.</sys:String>
<sys:String x:Key="Crash.Reason.ModConfigCrash.WithConfig">The configuration file {1} for the mod {0} could not be read.</sys:String>
<sys:String x:Key="Crash.Reason.ModInitializationFailed.Multiple">The following mods failed to initialize:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.ModInitializationFailed.Single">The mod {0} failed to initialize.</sys:String>
<sys:String x:Key="Crash.Reason.ModLoaderError.Generic">The mod loader may have provided an error message.</sys:String>
<sys:String x:Key="Crash.Reason.ModLoaderError.WithDetail">The mod loader provided the following error message:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.ModLoaderIncompatible">The mod loader version is incompatible with the mods:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.ModMixinFailed.Multiple">Mixin injection failed for the following mods:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.ModMixinFailed.None">Mixin injection failed for one or more mods.</sys:String>
<sys:String x:Key="Crash.Reason.ModMixinFailed.Single">Mixin injection failed for the mod {0}.</sys:String>
<sys:String x:Key="Crash.Reason.ModRequiresJava11">Some installed mods require Java 11 or later.</sys:String>
<sys:String x:Key="Crash.Reason.MultipleForgeInInstanceJson">The instance JSON contains multiple Forge version arguments.</sys:String>
<sys:String x:Key="Crash.Reason.NightConfigBug">Night Config encountered an error while reading configuration files.</sys:String>
<sys:String x:Key="Crash.Reason.NoAnalyzableFile">The game ran into a problem, but PCL CE could not find related log files to analyze.</sys:String>
<sys:String x:Key="Crash.Reason.OldForgeNewJavaIncompatible">An old Forge version is incompatible with the selected Java version.</sys:String>
<sys:String x:Key="Crash.Reason.OpenGl1282">A shader pack or resource pack may have triggered OpenGL error 1282.</sys:String>
<sys:String x:Key="Crash.Reason.OptiFineForgeIncompatible">OptiFine may be incompatible with the current Forge or Minecraft version.</sys:String>
<sys:String x:Key="Crash.Reason.OptiFineWorldLoadCrash">The selected OptiFine version may have caused the world to fail to load.</sys:String>
<sys:String x:Key="Crash.Reason.OutOfMemory">Minecraft ran out of memory.</sys:String>
<sys:String x:Key="Crash.Reason.ResourcePackTooLarge">The resource pack resolution may be too high, or the graphics card may not be powerful enough.</sys:String>
<sys:String x:Key="Crash.Reason.ShadersModWithOptiFine">Both Shaders Mod and OptiFine are installed.</sys:String>
<sys:String x:Key="Crash.Reason.SpecificBlock.Multiple">Some blocks in the world may have caused the error.</sys:String>
<sys:String x:Key="Crash.Reason.SpecificBlock.Single">The block {0} may have caused the error.</sys:String>
<sys:String x:Key="Crash.Reason.SpecificEntity.Multiple">Some entities in the world may have caused the error.</sys:String>
<sys:String x:Key="Crash.Reason.SpecificEntity.Single">The entity {0} may have caused the error.</sys:String>
<sys:String x:Key="Crash.Reason.StackKeyword.Multiple">PCL CE found possibly related keywords in the error stack trace:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.StackKeyword.Single">PCL CE found a possibly related keyword in the error stack trace: {0}</sys:String>
<sys:String x:Key="Crash.Reason.SuspectedMod.Multiple">The crash report suspects that the following mods caused the error:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.SuspectedMod.Single">The crash report suspects that the mod {0} caused the error.</sys:String>
<sys:String x:Key="Crash.Reason.TooManyModsIdLimit">Too many mods are installed, exceeding the ID limit of an older Minecraft version.</sys:String>
<sys:String x:Key="Crash.Reason.Unknown.Auto">PCL CE could not determine the exact cause of the crash.</sys:String>
<sys:String x:Key="Crash.Reason.Unknown.Manual">PCL CE could not determine the cause of this error.</sys:String>
<sys:String x:Key="Crash.Reason.UnknownFinding">PCL CE detected an error cause without details: {0}</sys:String>
<sys:String x:Key="Crash.Reason.UsingJdk">The game may be using a JDK, or the selected Java version may be too new.</sys:String>
<sys:String x:Key="Crash.Reason.UsingOpenJ9">The OpenJ9 Java runtime may be incompatible with the game, mods, or loader.</sys:String>
<sys:String x:Key="Crash.Reason.VeryShortOutput">The game output is too short for PCL CE to analyze. Raw output:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.X86JavaMemoryLimit.OnX64Os">The selected 32-bit Java runtime cannot allocate enough memory for the game.</sys:String>
<sys:String x:Key="Crash.Reason.X86JavaMemoryLimit.OnX86Os">The 32-bit operating system cannot provide enough memory for the game.</sys:String>
<!-- Crash.Report -->
<sys:String x:Key="Crash.Report.Export.Success">Crash report exported!</sys:String>
<sys:String x:Key="Crash.Report.SaveDialog.DefaultFileName">Crash Report-{0}.zip</sys:String>
<sys:String x:Key="Crash.Report.SaveDialog.Filter">Minecraft crash report (*.zip)|*.zip</sys:String>
<sys:String x:Key="Crash.Report.SaveDialog.Title">Select save location</sys:String>
<!-- Crash.Report.Environment -->
<sys:String x:Key="Crash.Report.Environment.Cpu">CPU{0}</sys:String>
<sys:String x:Key="Crash.Report.Environment.EnvironmentSection">- Environment information -</sys:String>
<sys:String x:Key="Crash.Report.Environment.Gpu">GPU {0}: {1} ({2} MiB, {3})</sys:String>
<sys:String x:Key="Crash.Report.Environment.InstanceSection">- Instance information -</sys:String>
<sys:String x:Key="Crash.Report.Environment.LauncherId">Identification code: {0}</sys:String>
<sys:String x:Key="Crash.Report.Environment.LauncherVersion">PCL CE version: {0}</sys:String>
<sys:String x:Key="Crash.Report.Environment.Log4j2NoLookups">Log4j2 NoLookups{0}</sys:String>
<sys:String x:Key="Crash.Report.Environment.MemoryAllocation">Memory allocation (allocated memory / installed physical memory): {0} / {1} GiB ({2} MiB)</sys:String>
<sys:String x:Key="Crash.Report.Environment.MinecraftFolder">Minecraft folder: {0}</sys:String>
<sys:String x:Key="Crash.Report.Environment.OperatingSystem">Operating system: {0} (64-bit: {1}, ARM64: {2})</sys:String>
<sys:String x:Key="Crash.Report.Environment.ProfileName">Profile name: {0} (login method: {1})</sys:String>
<sys:String x:Key="Crash.Report.Environment.ProfileSection">- Profile information -</sys:String>
<sys:String x:Key="Crash.Report.Environment.SelectedJava">Selected Java VM: {0}</sys:String>
<!-- Crash.Report.JarInJarMod -->
<sys:String x:Key="Crash.Report.JarInJarMod.DuplicateDescription">These mods may conflict with their bundled copies and cause crashes:</sys:String>
<sys:String x:Key="Crash.Report.JarInJarMod.DuplicateEntry">{0} [{1}] &lt;-&gt; bundled in {2} ({3})</sys:String>
<sys:String x:Key="Crash.Report.JarInJarMod.DuplicateHeuristic">(Matched by mod ID and may include false positives.)</sys:String>
<sys:String x:Key="Crash.Report.JarInJarMod.DuplicateNone">None</sys:String>
<sys:String x:Key="Crash.Report.JarInJarMod.DuplicateSection">=== Potentially duplicated mods (installed standalone and bundled via Jar-in-Jar) ===</sys:String>
<sys:String x:Key="Crash.Report.JarInJarMod.JarInJarNone">No Jar-in-Jar information found</sys:String>
<sys:String x:Key="Crash.Report.JarInJarMod.JarInJarSection">=== Jar-in-Jar information (enabled mods only) ===</sys:String>
<sys:String x:Key="Crash.Report.JarInJarMod.ModListDirectory">Directory: {0}</sys:String>
<sys:String x:Key="Crash.Report.JarInJarMod.ModListSection">=== Mod list ===</sys:String>
<!-- Crash.Suggestion -->
<sys:String x:Key="Crash.Suggestion.DisableTheseMods">Try disabling these mods one by one, then check whether the game still crashes.</sys:String>
<sys:String x:Key="Crash.Suggestion.DisableThisMod">Try disabling this mod, then check whether the game still crashes.</sys:String>
<sys:String x:Key="Crash.Suggestion.DuplicateMods">Each mod should be installed only once. Remove the duplicate mods and launch the game again.</sys:String>
<sys:String x:Key="Crash.Suggestion.ExportReport">When asking for help, send the crash report file instead of a photo or screenshot of this window.</sys:String>
<sys:String x:Key="Crash.Suggestion.ExtractedModFile">Remove extracted mod folders from the mods folder, then put the original .jar mod files there and try again.</sys:String>
<sys:String x:Key="Crash.Suggestion.FileOrContentValidationFailed">Delete the game and related mods, then download them again.</sys:String>
<sys:String x:Key="Crash.Suggestion.FollowLoaderInstructions">Follow the loader messages above or in the crash report to resolve the problem.</sys:String>
<sys:String x:Key="Crash.Suggestion.GraphicsDriver.Generic">Try updating or rolling back the graphics driver. If needed, switch to an older Java version.</sys:String>
<sys:String x:Key="Crash.Suggestion.GraphicsDriver.IntelOrIntegrated">If the device has a dedicated GPU, use it to launch PCL CE and Minecraft. You can also update or roll back the graphics driver.</sys:String>
<sys:String x:Key="Crash.Suggestion.IncompleteForgeInstallation">Reset or reinstall Forge in instance settings. Do not delete the libraries folder when packaging an instance.</sys:String>
<sys:String x:Key="Crash.Suggestion.InvalidModFileName">Remove or rename the affected mod file so that its name contains only English letters, numbers, hyphens, underscores, and periods.</sys:String>
<sys:String x:Key="Crash.Suggestion.JavaIncompatible">Choose a suitable Java version in launch settings based on the Minecraft version and mod requirements.</sys:String>
<sys:String x:Key="Crash.Suggestion.JavaTooNew">Switch to an older Java version in launch settings and try again.</sys:String>
<sys:String x:Key="Crash.Suggestion.LauncherOutdated">You are using an outdated version of PCL CE. Updating PCL CE may resolve this problem: Settings → Launcher → Check updates.</sys:String>
<sys:String x:Key="Crash.Suggestion.MissingMixinBootstrap">Check that MixinBootstrap, the mod loader, and related dependencies are installed correctly.</sys:String>
<sys:String x:Key="Crash.Suggestion.ModConfigCrash">Try deleting this mod's configuration file and launch again. If needed, update or disable the mod.</sys:String>
<sys:String x:Key="Crash.Suggestion.ModLoaderIncompatible">Open instance settings and adjust or reinstall the mod loader version.</sys:String>
<sys:String x:Key="Crash.Suggestion.ModMixinFailed">Disable mods step by step to find the one that is incompatible with the current environment or has a problem.</sys:String>
<sys:String x:Key="Crash.Suggestion.ModRequiresJava11">Switch to Java 11 or later in launch settings and try again.</sys:String>
<sys:String x:Key="Crash.Suggestion.MultipleForgeInInstanceJson">Reset or reinstall Forge in instance settings. Do not use another launcher to modify the Forge version.</sys:String>
<sys:String x:Key="Crash.Suggestion.NightConfigBug">Try deleting the related mod configuration files, or install Night Config Fixes.</sys:String>
<sys:String x:Key="Crash.Suggestion.OldForgeNewJavaIncompatible">Try updating Forge or switching to an older Java version.</sys:String>
<sys:String x:Key="Crash.Suggestion.OpenGl1282">Disable shader packs and remove recently added resource packs, then try again.</sys:String>
<sys:String x:Key="Crash.Suggestion.OptiFineForgeIncompatible">Check the compatible OptiFine and Forge versions, then reinstall matching versions.</sys:String>
<sys:String x:Key="Crash.Suggestion.OptiFineWorldLoadCrash">Try another OptiFine version, or remove OptiFine before entering the world again.</sys:String>
<sys:String x:Key="Crash.Suggestion.OutOfMemory">Increase the memory allocated in launch settings, and remove demanding resource packs, mods, or shader packs. You can also close other applications before launching the game.</sys:String>
<sys:String x:Key="Crash.Suggestion.ResourcePackTooLarge">Remove high-resolution resource packs or lower their resolution. You can also update the graphics driver.</sys:String>
<sys:String x:Key="Crash.Suggestion.ShadersModWithOptiFine">Remove Shaders Mod. OptiFine already provides shader support.</sys:String>
<sys:String x:Key="Crash.Suggestion.SpecificBlock.Multiple">Create a new world to test whether the game runs normally. If it does, blocks in the original world may be responsible.</sys:String>
<sys:String x:Key="Crash.Suggestion.SpecificBlock.Single">Create a new world to test whether the game runs normally. If it does, remove this block with a map editor or disable the mod that provides it.</sys:String>
<sys:String x:Key="Crash.Suggestion.SpecificEntity.Multiple">Create a new world and spawn entities to test whether the game runs normally. If it does, entities in the original world may be responsible.</sys:String>
<sys:String x:Key="Crash.Suggestion.SpecificEntity.Single">Create a new world and spawn this entity to test whether the game runs normally. If it does, remove the entity with a map editor or disable the mod that provides it.</sys:String>
<sys:String x:Key="Crash.Suggestion.StackKeyword">Check the mods or libraries related to these keywords. You can also export the crash report for more details.</sys:String>
<sys:String x:Key="Crash.Suggestion.TooManyModsIdLimit">Reduce the number of installed mods, or install a fix mod that extends the ID limit for older Minecraft versions.</sys:String>
<sys:String x:Key="Crash.Suggestion.UsingJdk">Switch to a regular JRE or another Java version that is suitable for this instance in launch settings.</sys:String>
<sys:String x:Key="Crash.Suggestion.UsingOpenJ9">Switch to HotSpot Java in launch settings and try again.</sys:String>
<sys:String x:Key="Crash.Suggestion.ViewReport">View the crash report for more details about how the error happened.</sys:String>
<sys:String x:Key="Crash.Suggestion.X86JavaMemoryLimit.OnX64Os">Install and select 64-bit Java in launch settings, then try again.</sys:String>
<sys:String x:Key="Crash.Suggestion.X86JavaMemoryLimit.OnX86Os">Use a 64-bit operating system and 64-bit Java, then try again.</sys:String>
<!-- Download -->
<sys:String x:Key="Download.Error.OperationFailed">A download page operation failed. See the details below.</sys:String>
<!-- Download.Comp -->
<sys:String x:Key="Download.Comp.Error.OperationFailed">A resource download operation failed. See the details below.</sys:String>
<sys:String x:Key="Download.Comp.Hint.ModrinthUnreachable">Unable to connect to Modrinth; only contents from CurseForge are being displayed. Results might be incomplete.&#xA;Please check your internet and try again.</sys:String>
<sys:String x:Key="Download.Comp.Link.ResolveFailed">Could not recognize or fetch the resource link</sys:String>
<sys:String x:Key="Download.Comp.Search.Hint">Search resources</sys:String>
<sys:String x:Key="Download.Comp.Sort.Downloads">Downloads</sys:String>
<sys:String x:Key="Download.Comp.Sort.Follows">Follows</sys:String>
<sys:String x:Key="Download.Comp.Sort.Latest">Latest</sys:String>
<sys:String x:Key="Download.Comp.Sort.Relevance">Relevance</sys:String>
<sys:String x:Key="Download.Comp.Sort.Updated">Recently updated</sys:String>
<!-- Download.Comp.Category -->
<sys:String x:Key="Download.Comp.Category.Adventure">Adventure</sys:String>
<sys:String x:Key="Download.Comp.Category.Animated">Animated</sys:String>
<sys:String x:Key="Download.Comp.Category.Audio">Audio</sys:String>
<sys:String x:Key="Download.Comp.Category.Automation">Automation</sys:String>
<sys:String x:Key="Download.Comp.Category.Biomes">Biomes</sys:String>
<sys:String x:Key="Download.Comp.Category.Cartoon">Cartoon</sys:String>
<sys:String x:Key="Download.Comp.Category.ColoredLighting">Colored Lighting</sys:String>
<sys:String x:Key="Download.Comp.Category.Combat">Combat</sys:String>
<sys:String x:Key="Download.Comp.Category.CoreShaders">Core Shaders</sys:String>
<sys:String x:Key="Download.Comp.Category.Creative">Creative</sys:String>
<sys:String x:Key="Download.Comp.Category.Cursed">Cursed</sys:String>
<sys:String x:Key="Download.Comp.Category.DataPack.Fantasy">Fantasy</sys:String>
<sys:String x:Key="Download.Comp.Category.Decoration">Decoration</sys:String>
<sys:String x:Key="Download.Comp.Category.Dimensions">Dimensions</sys:String>
<sys:String x:Key="Download.Comp.Category.Display">Display</sys:String>
<sys:String x:Key="Download.Comp.Category.Energy">Energy</sys:String>
<sys:String x:Key="Download.Comp.Category.Entities">Entities</sys:String>
<sys:String x:Key="Download.Comp.Category.Equipment">Equipment</sys:String>
<sys:String x:Key="Download.Comp.Category.Fantasy">Fantasy</sys:String>
<sys:String x:Key="Download.Comp.Category.Farming">Farming</sys:String>
<sys:String x:Key="Download.Comp.Category.Feature">Features</sys:String>
<sys:String x:Key="Download.Comp.Category.Fonts">Fonts</sys:String>
<sys:String x:Key="Download.Comp.Category.FoodCooking">Food &amp; Cooking</sys:String>
<sys:String x:Key="Download.Comp.Category.GameMechanics">Game Mechanics</sys:String>
<sys:String x:Key="Download.Comp.Category.Gui">GUI</sys:String>
<sys:String x:Key="Download.Comp.Category.Iris">Iris</sys:String>
<sys:String x:Key="Download.Comp.Category.Library">API &amp; Library</sys:String>
<sys:String x:Key="Download.Comp.Category.Locale">Locale</sys:String>
<sys:String x:Key="Download.Comp.Category.Magic">Magic</sys:String>
<sys:String x:Key="Download.Comp.Category.Medieval">Medieval</sys:String>
<sys:String x:Key="Download.Comp.Category.Mobs">Mobs</sys:String>
<sys:String x:Key="Download.Comp.Category.Mod.Mod">Mod</sys:String>
<sys:String x:Key="Download.Comp.Category.Mod.ModRelated">Mod-related</sys:String>
<sys:String x:Key="Download.Comp.Category.ModSupport">Mod Support</sys:String>
<sys:String x:Key="Download.Comp.Category.Models">Models</sys:String>
<sys:String x:Key="Download.Comp.Category.Modern">Modern</sys:String>
<sys:String x:Key="Download.Comp.Category.Multiplayer">Multiplayer</sys:String>
<sys:String x:Key="Download.Comp.Category.Optifine">OptiFine</sys:String>
<sys:String x:Key="Download.Comp.Category.Optimization">Optimization</sys:String>
<sys:String x:Key="Download.Comp.Category.OresResources">Ores &amp; Resources</sys:String>
<sys:String x:Key="Download.Comp.Category.Other">Other</sys:String>
<sys:String x:Key="Download.Comp.Category.PathTracing">Path Tracing</sys:String>
<sys:String x:Key="Download.Comp.Category.Pbr">PBR</sys:String>
<sys:String x:Key="Download.Comp.Category.Performance">Performance impact</sys:String>
<sys:String x:Key="Download.Comp.Category.PerformanceHigh">High</sys:String>
<sys:String x:Key="Download.Comp.Category.PerformanceLow">Low</sys:String>
<sys:String x:Key="Download.Comp.Category.PerformanceMedium">Medium</sys:String>
<sys:String x:Key="Download.Comp.Category.PerformancePotato">Potato</sys:String>
<sys:String x:Key="Download.Comp.Category.PipesLogistics">Energy, Fluid, and Item Transport</sys:String>
<sys:String x:Key="Download.Comp.Category.Realistic">Realistic</sys:String>
<sys:String x:Key="Download.Comp.Category.Redstone">Redstone</sys:String>
<sys:String x:Key="Download.Comp.Category.Reflections">Reflections</sys:String>
<sys:String x:Key="Download.Comp.Category.Resolution">Resolution</sys:String>
<sys:String x:Key="Download.Comp.Category.SemiRealistic">Semi-realistic</sys:String>
<sys:String x:Key="Download.Comp.Category.Server">Server</sys:String>
<sys:String x:Key="Download.Comp.Category.Simplistic">Simplistic</sys:String>
<sys:String x:Key="Download.Comp.Category.Steampunk">Steampunk</sys:String>
<sys:String x:Key="Download.Comp.Category.Storage">Storage</sys:String>
<sys:String x:Key="Download.Comp.Category.Structures">Structures</sys:String>
<sys:String x:Key="Download.Comp.Category.Style">Style</sys:String>
<sys:String x:Key="Download.Comp.Category.Technology">Technology</sys:String>
<sys:String x:Key="Download.Comp.Category.Themed">Themed</sys:String>
<sys:String x:Key="Download.Comp.Category.Transportation">Transportation</sys:String>
<sys:String x:Key="Download.Comp.Category.Tweaks">Tweaks</sys:String>
<sys:String x:Key="Download.Comp.Category.Utility">Utility</sys:String>
<sys:String x:Key="Download.Comp.Category.VanillaAvailable">Vanilla</sys:String>
<sys:String x:Key="Download.Comp.Category.VanillaLike">Vanilla-like</sys:String>
<sys:String x:Key="Download.Comp.Category.World.Adventure">Adventure</sys:String>
<sys:String x:Key="Download.Comp.Category.World.Creative">Creative</sys:String>
<sys:String x:Key="Download.Comp.Category.World.ModWorld">Mod World</sys:String>
<sys:String x:Key="Download.Comp.Category.World.Parkour">Parkour</sys:String>
<sys:String x:Key="Download.Comp.Category.World.Puzzle">Puzzle</sys:String>
<sys:String x:Key="Download.Comp.Category.World.Survival">Survival</sys:String>
<sys:String x:Key="Download.Comp.Category.WorldGen">World Gen</sys:String>
<!-- Download.Comp.Category.Modpack -->
<sys:String x:Key="Download.Comp.Category.Modpack.Exploration">Exploration</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.ExtraLarge">Extra Large</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.Ftb">FTB</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.Ftb.ToolTip">Published by FTB.</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.Hardcore">Hardcore</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.KitchenSink">Kitchen Sink</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.MapBased">Map Based</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.MiniGame">Mini Games</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.Quests">Quests</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.SciFi">Sci-Fi</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.Skyblock">Skyblock</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.SmallLight">Small &amp; Light</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.VanillaPlus">Vanilla+</sys:String>
<!-- Download.Comp.Category.ResourcePack -->
<sys:String x:Key="Download.Comp.Category.ResourcePack.Resolution128x">128x</sys:String>
<sys:String x:Key="Download.Comp.Category.ResourcePack.Resolution16x">16x</sys:String>
<sys:String x:Key="Download.Comp.Category.ResourcePack.Resolution256x">256x</sys:String>
<sys:String x:Key="Download.Comp.Category.ResourcePack.Resolution32x">32x</sys:String>
<sys:String x:Key="Download.Comp.Category.ResourcePack.Resolution48x">48x</sys:String>
<sys:String x:Key="Download.Comp.Category.ResourcePack.Resolution512xOrHigher">512x or higher</sys:String>
<sys:String x:Key="Download.Comp.Category.ResourcePack.Resolution64x">64x</sys:String>
<sys:String x:Key="Download.Comp.Category.ResourcePack.Resolution8xOrLower">8x or lower</sys:String>
<!-- Download.Comp.Dependency -->
<sys:String x:Key="Download.Comp.Dependency.Abort.Message">Installation was canceled.&#xA;&#xA;Reason:&#xA;{0}</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Abort.Title">Installation canceled</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Install.Cancel">Cancel installation</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Install.ListItem">- {0} ({1}) - {2} v{3}</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Install.Message">This mod requires the following dependencies:&#xA;&#xA;{0}</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Install.Title">Install mod dependencies</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Install.WithDependencies">Install mod and dependencies</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Install.WithoutDependencies">Download mod only (not recommended)</sys:String>
<sys:String x:Key="Download.Comp.Dependency.ResolveFailed.Continue">Continue download</sys:String>
<sys:String x:Key="Download.Comp.Dependency.ResolveFailed.Message">Failed to resolve the mod dependencies. Only the main mod will be downloaded.</sys:String>
<sys:String x:Key="Download.Comp.Dependency.ResolveFailed.Title">Failed to resolve dependencies</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Unresolved.ContinueWithoutDependencies">Continue download</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Unresolved.ListItem">- {0} {1}: {2}</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Unresolved.Message">The following required dependencies could not be resolved:&#xA;&#xA;{0}</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Unresolved.Title">Required dependencies could not be installed</sys:String>
<!-- Download.Comp.Detail -->
<sys:String x:Key="Download.Comp.Detail.CleanCache">Clean cache</sys:String>
<sys:String x:Key="Download.Comp.Detail.Clipboard.Detected.Message">A resource link was detected in the clipboard. Open the details page?</sys:String>
<sys:String x:Key="Download.Comp.Detail.Clipboard.Detected.Title">Resource link detected</sys:String>
<sys:String x:Key="Download.Comp.Detail.Clipboard.Fetching">Getting resource information...</sys:String>
<sys:String x:Key="Download.Comp.Detail.Clipboard.InvalidContent">Invalid resource content.</sys:String>
<sys:String x:Key="Download.Comp.Detail.CopyLink">Copy link</sys:String>
<sys:String x:Key="Download.Comp.Detail.CopyName">Copy name</sys:String>
<sys:String x:Key="Download.Comp.Detail.DescriptionNoTranslation">No description translation is available for this resource</sys:String>
<sys:String x:Key="Download.Comp.Detail.DescriptionTranslating">Getting description translation for {0}...</sys:String>
<sys:String x:Key="Download.Comp.Detail.DescriptionTranslationFailed">Failed to get description translation</sys:String>
<sys:String x:Key="Download.Comp.Detail.DescriptionTranslationResult">Original: {0}&#xA;Translation: {1}</sys:String>
<sys:String x:Key="Download.Comp.Detail.DownloadFile">Download file</sys:String>
<sys:String x:Key="Download.Comp.Detail.DownloadModpackFile">Download modpack file</sys:String>
<sys:String x:Key="Download.Comp.Detail.DownloadResource">{0} download: {1}</sys:String>
<sys:String x:Key="Download.Comp.Detail.DownloadWorldFile">Download world file</sys:String>
<sys:String x:Key="Download.Comp.Detail.Favorite">Favorite</sys:String>
<sys:String x:Key="Download.Comp.Detail.Favorites.Add">Added {0} to {1}</sys:String>
<sys:String x:Key="Download.Comp.Detail.Favorites.BulkAdd">Added {0} resources to {1}!</sys:String>
<sys:String x:Key="Download.Comp.Detail.Favorites.BulkAddWithFailures">Added {0} resources to {1}; {2} resources had already been added!</sys:String>
<sys:String x:Key="Download.Comp.Detail.Favorites.DefaultName">Default</sys:String>
<sys:String x:Key="Download.Comp.Detail.Favorites.FavoriteContextMenu">Add to {0}</sys:String>
<sys:String x:Key="Download.Comp.Detail.Favorites.Remove">Removed {0} from {1}</sys:String>
<sys:String x:Key="Download.Comp.Detail.Favorites.UnfavoriteContextMenu">Unfavorite {0}</sys:String>
<sys:String x:Key="Download.Comp.Detail.FindingApplicableInstance">Looking for an applicable instance...</sys:String>
<sys:String x:Key="Download.Comp.Detail.InputInstanceName">Enter instance name</sys:String>
<sys:String x:Key="Download.Comp.Detail.InstallWorld">Install world</sys:String>
<sys:String x:Key="Download.Comp.Detail.InstanceFilter">Instance: </sys:String>
<sys:String x:Key="Download.Comp.Detail.KnownButton">Got it</sys:String>
<sys:String x:Key="Download.Comp.Detail.LoaderFilter">Mod loader: </sys:String>
<sys:String x:Key="Download.Comp.Detail.McMod">MCMod</sys:String>
<sys:String x:Key="Download.Comp.Detail.ModpackDownload">Modpack download</sys:String>
<sys:String x:Key="Download.Comp.Detail.NoApplicableInstance">No applicable instance found in this Minecraft folder for this resource!</sys:String>
<sys:String x:Key="Download.Comp.Detail.OriginalModpack">Original modpack</sys:String>
<sys:String x:Key="Download.Comp.Detail.PrepareModpackInstall">Prepare for modpack installation</sys:String>
<sys:String x:Key="Download.Comp.Detail.ResourceFile.Filter">{0} file</sys:String>
<sys:String x:Key="Download.Comp.Detail.SelectSaveLocation">Select save location</sys:String>
<sys:String x:Key="Download.Comp.Detail.SelectWorldInstallLocation">Select world install location (saves folder)</sys:String>
<sys:String x:Key="Download.Comp.Detail.SelectedVersion">Selected version: {0}{1}</sys:String>
<sys:String x:Key="Download.Comp.Detail.Task.Cancelled">Modpack installation canceled: {0}</sys:String>
<sys:String x:Key="Download.Comp.Detail.Task.Failed">Modpack installation failed.&#xA;&#xA;Task: {0}&#xA;&#xA;Details:&#xA;{1}</sys:String>
<sys:String x:Key="Download.Comp.Detail.TranslateDescription">Translate description</sys:String>
<sys:String x:Key="Download.Comp.Detail.Unfavorite">Unfavorite</sys:String>
<sys:String x:Key="Download.Comp.Detail.VersionGroup.Old">Older</sys:String>
<sys:String x:Key="Download.Comp.Detail.VersionGroup.Other">Other</sys:String>
<sys:String x:Key="Download.Comp.Detail.VersionGroup.Snapshot">Snapshot</sys:String>
<sys:String x:Key="Download.Comp.Detail.VersionRecognitionDelayHint">Since the API updates slowly, it may not be able to recognize the Minecraft version that was just updated. It will be recognized normally after a few days.</sys:String>
<sys:String x:Key="Download.Comp.Detail.WorldDownload">World download</sys:String>
<sys:String x:Key="Download.Comp.Detail.WorldFile.Filter">World file|*.zip</sys:String>
<!-- Download.Comp.Detail.CompItem -->
<sys:String x:Key="Download.Comp.Detail.CompItem.AllVersions">All versions</sys:String>
<sys:String x:Key="Download.Comp.Detail.CompItem.AncientVersion">Before-release</sys:String>
<sys:String x:Key="Download.Comp.Detail.CompItem.Any">Any</sys:String>
<sys:String x:Key="Download.Comp.Detail.CompItem.EditionSuffix"> Edition</sys:String>
<sys:String x:Key="Download.Comp.Detail.CompItem.PreviewSuffix"> Preview</sys:String>
<sys:String x:Key="Download.Comp.Detail.CompItem.SnapshotOnly">Snapshot only</sys:String>
<sys:String x:Key="Download.Comp.Detail.CompItem.Unknown">Unknown</sys:String>
<sys:String x:Key="Download.Comp.Detail.CompItem.UnknownVersion">Unknown version</sys:String>
<!-- Download.Comp.Detail.FileList -->
<sys:String x:Key="Download.Comp.Detail.FileList.DependencyCount">{0} dependencies</sys:String>
<sys:String x:Key="Download.Comp.Detail.FileList.GameVersion">Game version {0}</sys:String>
<sys:String x:Key="Download.Comp.Detail.FileList.OptionalDependencies">Optional dependencies</sys:String>
<sys:String x:Key="Download.Comp.Detail.FileList.ReleaseType.Alpha">Alpha</sys:String>
<sys:String x:Key="Download.Comp.Detail.FileList.ReleaseType.Beta">Beta</sys:String>
<sys:String x:Key="Download.Comp.Detail.FileList.ReleaseType.Release">Release</sys:String>
<sys:String x:Key="Download.Comp.Detail.FileList.RequiredDependencies">Required dependencies</sys:String>
<sys:String x:Key="Download.Comp.Detail.FileList.Updated">Updated {0}</sys:String>
<sys:String x:Key="Download.Comp.Detail.FileList.VersionList">Version list</sys:String>
<!-- Download.Comp.Favorites -->
<sys:String x:Key="Download.Comp.Favorites.Action.Manage">Manage</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Action.Unfavorite">Unfavorite</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Cancel">Unfavorite</sys:String>
<sys:String x:Key="Download.Comp.Favorites.CardTitle">{0} ({1})</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Category.Mod">Mod ({0})</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Deselect">Deselect</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Download">Download selected</sys:String>
<sys:String x:Key="Download.Comp.Favorites.EditNote">Edit note</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Empty.Description">You can click the favorite button on the resource detail page to add a resource to favorites.</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Empty.Title">No favorites yet</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Error.OperationFailed">A favorites operation failed. See the details below.</sys:String>
<sys:String x:Key="Download.Comp.Favorites.GetInfoFailed">Online information for some resources failed to load. Click to view details.</sys:String>
<sys:String x:Key="Download.Comp.Favorites.LoaderName.BatchDownload">Batch download resources ({0})</sys:String>
<sys:String x:Key="Download.Comp.Favorites.LoaderName.BatchDownloadSuitable">Batch download applicable resources</sys:String>
<sys:String x:Key="Download.Comp.Favorites.LoaderName.QueryInfo">Query resource information</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Loading">Loading favorites list...</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Manage">Manage</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Menu.Delete">Delete current favorites folder</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Menu.Import">Import favorites</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Menu.New">Create favorites folder</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Menu.Rename">Rename favorites folder</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Menu.Share">Share current favorites</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Search.Hint">Search favorites</sys:String>
<sys:String x:Key="Download.Comp.Favorites.SearchResults.Title">Search results ({0})</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Selection.Placeholder">8 files selected</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Share">Share selected</sys:String>
<sys:String x:Key="Download.Comp.Favorites.UnknownType">Unknown type</sys:String>
<!-- Download.Comp.Favorites.Dialog -->
<sys:String x:Key="Download.Comp.Favorites.Dialog.BulkDownload.Content">Bulk download is still in testing.&#xA;Using this feature to download mods will not automatically download dependencies.&#xA;Please think through your needs and carefully check your selections before downloading to avoid wasting time and bandwidth.</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.BulkDownload.Title">Use this feature?</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Delete.Confirm">Are you sure you want to delete the {0} favorites folder?&#xA;This favorites folder has {1} favorite items&#xA;Favorites folder ID: {2}&#xA;This action cannot be undone!</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Delete.Title">Deletion confirmation</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.EditNote.Title">Edit note</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.GetFailed.Content">The following resources failed to load due to deleted online resources, network issues, or other reasons (shown by resource ID).</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.GetFailed.CopyIds">Copy these IDs</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.GetFailed.Remove">Remove these favorites</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.GetFailed.Title">Some favorites failed to load</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Import.CurrentFolder">Current folder</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Import.Hint">e.g. ["23333"]</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Import.Input">Enter shared favorites</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Import.New">New folder name</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Import.NewFolder">New folder</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Import.NewHint">Enter new folder name</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Import.Type">Do you want to add the shared favorites to the current favorites folder or to a new favorites folder?</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.New.Title">Create favorites folder</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.NewPrompt">Enter new folder name</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Rename.Title">Enter new name</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.SelectLoader.Title">Select desired loader</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.SelectVersion.Title">Select desired game version</sys:String>
<!-- Download.Comp.Favorites.Hint -->
<sys:String x:Key="Download.Comp.Favorites.Hint.Deleted">Favorites folder deleted</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Hint.LastCollection">You cannot delete the last favorites folder</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Hint.LoadingVersions">Please wait while detailed version support is being queried. This may take a while...</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Hint.NoResource">No resource exists for the specified loader and same game version</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Hint.NothingShared">Nothing to share!</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Hint.Removed">Related favorites removed</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Hint.SelectLoader">The selected mods do not support the same loader</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Hint.SelectSaveLocation">Selected {0}; now select a save location below</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Hint.SelectedCount">{0} favorite items selected</sys:String>
<!-- Download.Comp.Filter -->
<sys:String x:Key="Download.Comp.Filter.InstallModpack.ToolTip">Install the modpack into the currently selected Minecraft folder.</sys:String>
<sys:String x:Key="Download.Comp.Filter.Loader">Loader</sys:String>
<sys:String x:Key="Download.Comp.Filter.Loader.Any">Any</sys:String>
<sys:String x:Key="Download.Comp.Filter.Loader.AnyShader">Any loader</sys:String>
<sys:String x:Key="Download.Comp.Filter.Loader.VanillaAvailable">Vanilla</sys:String>
<sys:String x:Key="Download.Comp.Filter.Loader.VanillaAvailable.ToolTip">It can be loaded as a resource pack and used directly in the vanilla game.</sys:String>
<sys:String x:Key="Download.Comp.Filter.Reset">Reset filters</sys:String>
<sys:String x:Key="Download.Comp.Filter.Sort">Sorting</sys:String>
<sys:String x:Key="Download.Comp.Filter.Source">Source</sys:String>
<sys:String x:Key="Download.Comp.Filter.Tag">Tags</sys:String>
<sys:String x:Key="Download.Comp.Filter.Version">Version</sys:String>
<sys:String x:Key="Download.Comp.Filter.Version.AllInputAvailable">All (input available)</sys:String>
<sys:String x:Key="Download.Comp.Filter.Version.Any">Any</sys:String>
<!-- Download.Comp.List -->
<sys:String x:Key="Download.Comp.List.NoMatchingResults">No matching results</sys:String>
<sys:String x:Key="Download.Comp.List.NoResults">No results, please try searching in English</sys:String>
<sys:String x:Key="Download.Comp.List.NoResultsSimple">No search results</sys:String>
<sys:String x:Key="Download.Comp.List.Source.CurseForgeUnavailable">Unable to connect to CurseForge; only contents from Modrinth are being displayed. Search results might be incomplete.&#xA;Please check your internet and try again.</sys:String>
<sys:String x:Key="Download.Comp.List.Source.ModrinthUnavailable">Unable to connect to Modrinth; only contents from CurseForge are being displayed. Search results might be incomplete.&#xA;Please check your internet and try again.</sys:String>
<sys:String x:Key="Download.Comp.List.Source.ResourceFetch">Community resource fetching: {0}</sys:String>
<!-- Download.Comp.List.Loading -->
<sys:String x:Key="Download.Comp.List.Loading.DataPack">Getting data pack list...</sys:String>
<sys:String x:Key="Download.Comp.List.Loading.Mod">Getting mod list...</sys:String>
<sys:String x:Key="Download.Comp.List.Loading.Modpack">Getting modpack list...</sys:String>
<sys:String x:Key="Download.Comp.List.Loading.Plugin">Getting plugin list...</sys:String>
<sys:String x:Key="Download.Comp.List.Loading.ResourcePack">Getting resource pack list...</sys:String>
<sys:String x:Key="Download.Comp.List.Loading.Schematic">Getting schematic list...</sys:String>
<sys:String x:Key="Download.Comp.List.Loading.Shader">Getting shader list...</sys:String>
<sys:String x:Key="Download.Comp.List.Loading.Unknown">Getting unknown list...</sys:String>
<sys:String x:Key="Download.Comp.List.Loading.World">Getting world list...</sys:String>
<!-- Download.Comp.List.Search -->
<sys:String x:Key="Download.Comp.List.Search.DataPack">Search data packs</sys:String>
<sys:String x:Key="Download.Comp.List.Search.Mod">Search mods</sys:String>
<sys:String x:Key="Download.Comp.List.Search.Modpack">Search modpacks</sys:String>
<sys:String x:Key="Download.Comp.List.Search.Plugin">Search plugins</sys:String>
<sys:String x:Key="Download.Comp.List.Search.ResourcePack">Search resource packs</sys:String>
<sys:String x:Key="Download.Comp.List.Search.Schematic">Search schematics</sys:String>
<sys:String x:Key="Download.Comp.List.Search.Shader">Search shaders</sys:String>
<sys:String x:Key="Download.Comp.List.Search.Unknown">Search unknown</sys:String>
<sys:String x:Key="Download.Comp.List.Search.World">Search worlds</sys:String>
<!-- Download.Comp.QuickDownload -->
<sys:String x:Key="Download.Comp.QuickDownload.Btn.ToolTip">Download the latest version</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.ChooseInstance.Title">Select a target Minecraft instance</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.ChooseMethod.AskInstance">Select an instance…</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.ChooseMethod.AskPath">Select a folder…</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.ChooseMethod.CurrentInstance">Download to the current instance</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.ChooseMethod.Title">Select a download destination</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.Hint.DownloadStarted">Started downloading: {0}</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.Hint.Loading">Getting resource version info, please wait…</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.Hint.NoCompatibleFile">No compatible version found for this instance</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.Hint.NoCompatibleInstance">No compatible Minecraft instances found</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.Hint.NoFile">No downloadable files found</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.Hint.NoInstance">No Minecraft instance selected!</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.Hint.SelectFolder">Select a folder to save the file</sys:String>
<!-- Download.Comp.Type -->
<sys:String x:Key="Download.Comp.Type.Any">Any</sys:String>
<sys:String x:Key="Download.Comp.Type.DataPack">Data pack</sys:String>
<sys:String x:Key="Download.Comp.Type.Mod">Mod</sys:String>
<sys:String x:Key="Download.Comp.Type.Modpack">Modpack</sys:String>
<sys:String x:Key="Download.Comp.Type.Only">Only {0}</sys:String>
<sys:String x:Key="Download.Comp.Type.Plugin">Plugin</sys:String>
<sys:String x:Key="Download.Comp.Type.ResourcePack">Resource pack</sys:String>
<sys:String x:Key="Download.Comp.Type.Schematic">Schematic</sys:String>
<sys:String x:Key="Download.Comp.Type.Shader">Shader</sys:String>
<sys:String x:Key="Download.Comp.Type.Unknown">Unknown</sys:String>
<sys:String x:Key="Download.Comp.Type.World">World</sys:String>
<!-- Download.Install -->
<sys:String x:Key="Download.Install.Error.OperationFailed">An installation version list operation failed. See the details below.</sys:String>
<sys:String x:Key="Download.Install.Hint.MinecraftBack">Click the Minecraft tile to return!</sys:String>
<sys:String x:Key="Download.Install.InstanceIsolation.Warning.Cancel">Cancel</sys:String>
<sys:String x:Key="Download.Install.InstanceIsolation.Warning.Continue">Continue</sys:String>
<sys:String x:Key="Download.Install.InstanceIsolation.Warning.Message">Instance isolation is not enabled, causing multiple instances to share the same mods folder.&#xA;As a result, Minecraft may crash when switching instances due to incompatible mods being loaded.&#xA;It is strongly recommended to enable instance isolation under "Settings → Launch options → Instance isolation" before proceeding with the installation.</sys:String>
<sys:String x:Key="Download.Install.InstanceIsolation.Warning.Title">Instance isolation</sys:String>
<sys:String x:Key="Download.Install.StartDownload">Download</sys:String>
<!-- Download.Install.Compat -->
<sys:String x:Key="Download.Install.Compat.CompatForgeSpecificOnly">Compatible only with certain Forge versions</sys:String>
<sys:String x:Key="Download.Install.Compat.IncompatibleWithLiteLoader">Incompatible with LiteLoader</sys:String>
<sys:String x:Key="Download.Install.Compat.IncompatibleWithLoader">Incompatible with {0}</sys:String>
<sys:String x:Key="Download.Install.Compat.IncompatibleWithOptiFine">Incompatible with OptiFine</sys:String>
<sys:String x:Key="Download.Install.Compat.OptiFabricOriginsRequired">Incompatible with older versions of Fabric, please download OptiFabric Origins manually.</sys:String>
<sys:String x:Key="Download.Install.Compat.RequiresFabric">Requires Fabric</sys:String>
<sys:String x:Key="Download.Install.Compat.RequiresLegacyFabric">Requires Legacy Fabric</sys:String>
<sys:String x:Key="Download.Install.Compat.RequiresOptiFine">Requires OptiFine</sys:String>
<sys:String x:Key="Download.Install.Compat.RequiresOptiFineAndFabric">Requires OptiFine &amp; Fabric</sys:String>
<!-- Download.Install.State -->
<sys:String x:Key="Download.Install.State.CanAdd">Installable</sys:String>
<sys:String x:Key="Download.Install.State.GetFailed">Failed to get version list: {0}</sys:String>
<sys:String x:Key="Download.Install.State.GetVersionListFailed">Failed to get version list: {0}</sys:String>
<sys:String x:Key="Download.Install.State.Getting">Obtaining...</sys:String>
<sys:String x:Key="Download.Install.State.Loading">Loading...</sys:String>
<sys:String x:Key="Download.Install.State.NoAvailableVersion">No available version</sys:String>
<sys:String x:Key="Download.Install.State.NoVersion">No version available</sys:String>
<sys:String x:Key="Download.Install.State.UnknownUnloaded">Unknown error, the status is Unloaded</sys:String>
<!-- Download.Install.Warning -->
<sys:String x:Key="Download.Install.Warning.FabricApi">Most of the mods will be unable to run if Fabric API is not installed!</sys:String>
<sys:String x:Key="Download.Install.Warning.LegacyFabricApi">Most of the mods will be unable to run if Legacy Fabric API is not installed!</sys:String>
<sys:String x:Key="Download.Install.Warning.LegacyOptiFabric">Please install "LegacyOptiFabric" mod after the installation, otherwise OptiFine will be unable to run!</sys:String>
<sys:String x:Key="Download.Install.Warning.ModOptiFine">OptiFine has poor compatibility with some mods. Please install it cautiously.</sys:String>
<sys:String x:Key="Download.Install.Warning.OptiFabric">OptiFine will be unable to run if OptiFabric is not installed!</sys:String>
<sys:String x:Key="Download.Install.Warning.OptiFabricOld">Please install "OptiFabric Origins" mod after the installation, otherwise OptiFine will be unable to run!</sys:String>
<!-- Download.Left -->
<sys:String x:Key="Download.Left.Cleanroom">Cleanroom</sys:String>
<sys:String x:Key="Download.Left.CommunityResources">Community resources</sys:String>
<sys:String x:Key="Download.Left.DataPack">Data packs</sys:String>
<sys:String x:Key="Download.Left.Error.UnknownSubPageType">Unknown download subpage type: {0}</sys:String>
<sys:String x:Key="Download.Left.Fabric">Fabric</sys:String>
<sys:String x:Key="Download.Left.Favorites">Favorites</sys:String>
<sys:String x:Key="Download.Left.Forge">Forge</sys:String>
<sys:String x:Key="Download.Left.Hint.Refreshing">Refreshing...</sys:String>
<sys:String x:Key="Download.Left.Installation">Installation</sys:String>
<sys:String x:Key="Download.Left.LabyMod">LabyMod</sys:String>
<sys:String x:Key="Download.Left.LegacyFabric">Legacy Fabric</sys:String>
<sys:String x:Key="Download.Left.LiteLoader">LiteLoader</sys:String>
<sys:String x:Key="Download.Left.Minecraft">Minecraft</sys:String>
<sys:String x:Key="Download.Left.Mod">Mods</sys:String>
<sys:String x:Key="Download.Left.Modpack">Modpacks</sys:String>
<sys:String x:Key="Download.Left.NeoForge">NeoForge</sys:String>
<sys:String x:Key="Download.Left.Optifine">OptiFine</sys:String>
<sys:String x:Key="Download.Left.ResourcePack">Resource packs</sys:String>
<sys:String x:Key="Download.Left.Shader">Shaders</sys:String>
<sys:String x:Key="Download.Left.World">Worlds</sys:String>
<!-- Download.Source -->
<sys:String x:Key="Download.Source.CleanroomOfficial">Cleanroom official source</sys:String>
<sys:String x:Key="Download.Source.FabricOfficial">Fabric official source</sys:String>
<sys:String x:Key="Download.Source.ForgeOfficial">Forge official source</sys:String>
<sys:String x:Key="Download.Source.LegacyFabricOfficial">Legacy Fabric official source</sys:String>
<sys:String x:Key="Download.Source.LiteLoaderOfficial">LiteLoader official source</sys:String>
<sys:String x:Key="Download.Source.MojangOfficial">Mojang official source</sys:String>
<sys:String x:Key="Download.Source.NeoForgeOfficial">NeoForge official source</sys:String>
<sys:String x:Key="Download.Source.OptiFineOfficial">OptiFine official source</sys:String>
<sys:String x:Key="Download.Source.Tag.Uvmc">UVMC Exclusive</sys:String>
<!-- Download.Version -->
<sys:String x:Key="Download.Version.AllVersions">All versions ({0})</sys:String>
<sys:String x:Key="Download.Version.Changelog">Changelog</sys:String>
<sys:String x:Key="Download.Version.Cleanroom.Intro.Description">Cleanroom is a Forge-based mod loader for 1.12.2, theoretically compatible with 99% of Forge mods.</sys:String>
<sys:String x:Key="Download.Version.Cleanroom.Intro.Title">Introduction of Cleanroom</sys:String>
<sys:String x:Key="Download.Version.Cleanroom.LoadingList">Getting Cleanroom list...</sys:String>
<sys:String x:Key="Download.Version.DownloadServer">Download server</sys:String>
<sys:String x:Key="Download.Version.Fabric.Intro.Description">Fabric Loader is a lightweight mod loader for modern versions of Minecraft.&#xA;You must install it before using any Fabric mods. This page offers the Fabric installer for download. After downloading, you need to complete the installation process manually through the installer.</sys:String>
<sys:String x:Key="Download.Version.Fabric.Intro.Title">Introduction of Fabric</sys:String>
<sys:String x:Key="Download.Version.Fabric.LoadingList">Getting Fabric list...</sys:String>
<sys:String x:Key="Download.Version.Forge.Intro.Description">Forge is a mod loader. You must install it before using any Forge mods.</sys:String>
<sys:String x:Key="Download.Version.Forge.Intro.Title">Introduction of Forge</sys:String>
<sys:String x:Key="Download.Version.Forge.LoadingList">Getting Forge list...</sys:String>
<sys:String x:Key="Download.Version.Forge.Type">Type: {0}</sys:String>
<sys:String x:Key="Download.Version.Fresh.Development">Latest development</sys:String>
<sys:String x:Key="Download.Version.Fresh.Latest">Latest</sys:String>
<sys:String x:Key="Download.Version.Fresh.Stable">Latest stable</sys:String>
<sys:String x:Key="Download.Version.Installer.Fabric.Filter">Fabric installer (*.jar)|*.jar</sys:String>
<sys:String x:Key="Download.Version.Installer.LabyMod.Filter">LabyMod installer (*.jar)|*.jar</sys:String>
<sys:String x:Key="Download.Version.Installer.LegacyFabric.Filter">Legacy Fabric installer (*.jar)|*.jar</sys:String>
<sys:String x:Key="Download.Version.LabyMod.Intro.Description">LabyMod is an optimization client for Minecraft.&#xA;This page offers the LabyMod installer for download. After downloading, you need to complete the installation process manually through the installer.</sys:String>
<sys:String x:Key="Download.Version.LabyMod.Intro.Title">Introduction of LabyMod</sys:String>
<sys:String x:Key="Download.Version.LabyMod.LoadingList">Getting LabyMod list...</sys:String>
<sys:String x:Key="Download.Version.Latest.Development">Latest development, released on {0}</sys:String>
<sys:String x:Key="Download.Version.Latest.Release">Latest release, released on {0}</sys:String>
<sys:String x:Key="Download.Version.Latest.Title">Latest version</sys:String>
<sys:String x:Key="Download.Version.LegacyFabric.Intro.Description">Legacy Fabric is a backport of Fabric for older versions of Minecraft.&#xA;You must install it before using any Legacy Fabric mods. This page offers the Legacy Fabric installer for download. After downloading, you need to complete the installation process manually through the installer.</sys:String>
<sys:String x:Key="Download.Version.LegacyFabric.Intro.Title">Introduction of Legacy Fabric</sys:String>
<sys:String x:Key="Download.Version.LegacyFabric.LoadingList">Getting Legacy Fabric list...</sys:String>
<sys:String x:Key="Download.Version.LiteLoader.Intro.Description">Similar to Forge, LiteLoader can be used to load LiteLoader mods in older versions of Minecraft.</sys:String>
<sys:String x:Key="Download.Version.LiteLoader.Intro.Title">Introduction of LiteLoader</sys:String>
<sys:String x:Key="Download.Version.LiteLoader.LoadingList">Getting LiteLoader list...</sys:String>
<sys:String x:Key="Download.Version.LoadingList">Getting version list...</sys:String>
<sys:String x:Key="Download.Version.NeoForge.Intro.Description">NeoForge is a mod loader for Minecraft 1.20.1+. You must install it before using any NeoForge mods. Additionally, it is compatible with some Forge mods.&#xA;This page offers the NeoForge installer for download. After downloading, you need to complete the installation process manually through the installer.</sys:String>
<sys:String x:Key="Download.Version.NeoForge.Intro.Title">Introduction of NeoForge</sys:String>
<sys:String x:Key="Download.Version.NeoForge.LoadingList">Getting NeoForge list...</sys:String>
<sys:String x:Key="Download.Version.OpenWebsite">Official website</sys:String>
<sys:String x:Key="Download.Version.Optifine.CompatibleForge">Compatible with Forge {0}</sys:String>
<sys:String x:Key="Download.Version.Optifine.IncompatibleForge">Incompatible with Forge</sys:String>
<sys:String x:Key="Download.Version.Optifine.Intro.Description">OptiFine allows you to install shaders, use HD resource packs, and improve game performance significantly.</sys:String>
<sys:String x:Key="Download.Version.Optifine.Intro.Title">Introduction of OptiFine</sys:String>
<sys:String x:Key="Download.Version.Optifine.LoadingList">Getting OptiFine list...</sys:String>
<sys:String x:Key="Download.Version.Optifine.Snapshot">Snapshot</sys:String>
<sys:String x:Key="Download.Version.ReleaseDate">Released on {0}</sys:String>
<sys:String x:Key="Download.Version.SaveAs">Save as</sys:String>
<sys:String x:Key="Download.Version.SaveInstaller">Save installer</sys:String>
<sys:String x:Key="Download.Version.SelectSaveLocation">Select save location</sys:String>
<sys:String x:Key="Download.Version.VersionList">Version list</sys:String>
<sys:String x:Key="Download.Version.VersionListCount">Version list ({0})</sys:String>
<sys:String x:Key="Download.Version.ViewAllVersions">Show all versions</sys:String>
<!-- Download.Version.Type -->
<sys:String x:Key="Download.Version.Type.AprilFools">April Fools'</sys:String>
<sys:String x:Key="Download.Version.Type.BeforeRelease">Before-release</sys:String>
<sys:String x:Key="Download.Version.Type.Development">Development</sys:String>
<sys:String x:Key="Download.Version.Type.Installer">Installer</sys:String>
<sys:String x:Key="Download.Version.Type.Preview">Preview</sys:String>
<sys:String x:Key="Download.Version.Type.Recommended">Recommended</sys:String>
<sys:String x:Key="Download.Version.Type.Release">Release</sys:String>
<sys:String x:Key="Download.Version.Type.Snapshot">Snapshot</sys:String>
<sys:String x:Key="Download.Version.Type.Stable">Stable</sys:String>
<!-- Event -->
<sys:String x:Key="Event.Download.Confirm">About to download from this URL:&#xA;{0}</sys:String>
<sys:String x:Key="Event.Error.DownloadUrlRequired">EventData must be a URL starting with http:// or https://.&#xA;PCL CE does not support other download protocols.</sys:String>
<sys:String x:Key="Event.Error.ExecutionFailed">Event execution failed ({0}, {1})</sys:String>
<sys:String x:Key="Event.Error.FileNotFound">Local file not found for EventData: {0}</sys:String>
<sys:String x:Key="Event.Error.MissingArgs">EventType {0} requires at least 2 parameters separated by |, e.g. {1}</sys:String>
<sys:String x:Key="Event.Error.Title">Event execution failed</sys:String>
<sys:String x:Key="Event.Error.UnknownType">Unknown event type: {0}&#xA;Please check if the EventType is correct, or if PCL CE is up to date.</sys:String>
<sys:String x:Key="Event.Error.UrlRequired">EventData must be a URL.&#xA;If you want to launch a program, change EventType to OpenFile.</sys:String>
<sys:String x:Key="Event.LaunchGame.SelectVersion">Please select a Minecraft instance first!</sys:String>
<sys:String x:Key="Event.LaunchGame.Starting">Starting {0}...</sys:String>
<sys:String x:Key="Event.OpenUrl.Opening">Opening, please wait: {0}</sys:String>
<sys:String x:Key="Event.Refresh.NotSupported">The current page does not support refresh!</sys:String>
<sys:String x:Key="Event.Refresh.Success">Refreshed!</sys:String>
<sys:String x:Key="Event.Safety.ConfirmMessage">About to execute: {0}&#xA;Please confirm there are no security risks before continuing.</sys:String>
<sys:String x:Key="Event.Safety.ContinueAlways">Continue and do not ask again</sys:String>
<sys:String x:Key="Event.Safety.DangerousSettingBlocked">A custom homepage cannot write the security-sensitive setting: {0}</sys:String>
<sys:String x:Key="Event.Safety.Title">Confirm execution</sys:String>
<sys:String x:Key="Event.Sanitize.UnknownTypeHint">[{0}] is an unrecognized EventType and has been removed from the homepage.</sys:String>
<sys:String x:Key="Event.Sanitize.UnsupportedTypeHint">[{0}] is an unsupported original PCL EventType and has been removed from the homepage.</sys:String>
<sys:String x:Key="Event.Setting.Written">Setting written: {0} → {1}</sys:String>
<sys:String x:Key="Event.Variable.Written">Variable written: {0} → {1}</sys:String>
<!-- Instance -->
<sys:String x:Key="Instance.Error.OperationFailed">An instance operation failed. See the details below.</sys:String>
<sys:String x:Key="Instance.Install.Action.ModifyLabel">Modify</sys:String>
<sys:String x:Key="Instance.Install.Action.StartModify">Modify</sys:String>
<sys:String x:Key="Instance.Install.Action.StartReset">Reset</sys:String>
<sys:String x:Key="Instance.Install.Error.OperationFailed">An instance installation operation failed. See the details below.</sys:String>
<sys:String x:Key="Instance.Install.NoExtraInstall">No extra installation</sys:String>
<sys:String x:Key="Instance.Install.Reset.Message">You are resetting the current instance.&#xA;PCL CE will re-download the files required for this instance from the internet and reinstall mod loaders (if any).&#xA;This operation will not lose your saves, mods, resource packs, etc.</sys:String>
<sys:String x:Key="Instance.Install.Reset.Title">Reset this instance</sys:String>
<!-- Instance.Export -->
<sys:String x:Key="Instance.Export.Error.OperationFailed">An instance export operation failed. See the details below.</sys:String>
<sys:String x:Key="Instance.Export.ExportTask.Prefix">Export modpack:</sys:String>
<sys:String x:Key="Instance.Export.ModrinthWarning">Due to Modrinth modpack specifications, exported modpacks will not include OptiFine.</sys:String>
<sys:String x:Key="Instance.Export.MrpackFilter">Modrinth modpack file (*.mrpack)|*.mrpack</sys:String>
<sys:String x:Key="Instance.Export.NetCheckAllFailed.Message">Failed to obtain file info online. Continue export?&#xA;&#xA;If you continue, all files will be bundled directly.&#xA;Since redistribution may violate usage terms, please avoid publishing the exported modpack!</sys:String>
<sys:String x:Key="Instance.Export.NetCheckAllFailed.Title">File info retrieval failed</sys:String>
<sys:String x:Key="Instance.Export.NetCheckPartialFailed.Message">Failed to obtain info for some files online. Continue export?&#xA;&#xA;If you continue, files without info will be bundled directly.&#xA;Since redistribution may violate usage terms, please avoid publishing the exported modpack!</sys:String>
<sys:String x:Key="Instance.Export.NetCheckPartialFailed.Title">Partial file info retrieval failed</sys:String>
<sys:String x:Key="Instance.Export.OptionList.FromConfig">Read from config file</sys:String>
<sys:String x:Key="Instance.Export.OptionListTitle">Export content list</sys:String>
<sys:String x:Key="Instance.Export.PackName">Modpack name</sys:String>
<sys:String x:Key="Instance.Export.PackPathInvalid.Title">Cannot use the export path specified in config</sys:String>
<sys:String x:Key="Instance.Export.PackPathInvalid.WithDetail">The export path specified in the configuration file cannot be used.&#xA;&#xA;Specified path: {0}&#xA;&#xA;Details:&#xA;{1}</sys:String>
<sys:String x:Key="Instance.Export.PackVersion">Modpack version</sys:String>
<sys:String x:Key="Instance.Export.PathError">Specified path: {0}</sys:String>
<sys:String x:Key="Instance.Export.ReadFailed">Failed to read config file: {0}</sys:String>
<sys:String x:Key="Instance.Export.ReadSuccess">Config file loaded: {0}</sys:String>
<sys:String x:Key="Instance.Export.SaveSuccess">Config file saved: {0}</sys:String>
<sys:String x:Key="Instance.Export.SelectConfigFile">Select config file</sys:String>
<sys:String x:Key="Instance.Export.SelectFileLocation">Select file location</sys:String>
<sys:String x:Key="Instance.Export.SelectSaveLocation">Select export location</sys:String>
<sys:String x:Key="Instance.Export.StartExport">Export</sys:String>
<sys:String x:Key="Instance.Export.Task.CopyContent">Copy export contents</sys:String>
<sys:String x:Key="Instance.Export.Task.CreateArchive">Create archive</sys:String>
<sys:String x:Key="Instance.Export.Task.DownloadPclRelease">Download PCL CE release</sys:String>
<sys:String x:Key="Instance.Export.Task.FetchFileInfo">Get file information</sys:String>
<sys:String x:Key="Instance.Export.ZipFilter">ZIP file (*.zip)|*.zip</sys:String>
<!-- Instance.Export.Advanced -->
<sys:String x:Key="Instance.Export.Advanced.BundleFiles">Bundle resource files to avoid downloading them on import</sys:String>
<sys:String x:Key="Instance.Export.Advanced.BundleFiles.ToolTip">Place Mods, resource packs, and shader packs directly into the modpack so they do not need to be downloaded during import.&#xA;Only consider this when you cannot reliably connect to CurseForge or Modrinth.</sys:String>
<sys:String x:Key="Instance.Export.Advanced.BundleHint">Only bundle resource files when you cannot reliably connect to CurseForge or Modrinth.&#xA;Do not publicly distribute modpacks with bundled resource files, as this may violate some mods' ToS!</sys:String>
<sys:String x:Key="Instance.Export.Advanced.ConfigHint">The configuration file contains more advanced options, such as precise control over exported files and modpack storage location.&#xA;To modify these options, click "Save config" first, then import after editing the configuration file.</sys:String>
<sys:String x:Key="Instance.Export.Advanced.ExportConfig">Export config</sys:String>
<sys:String x:Key="Instance.Export.Advanced.ImportConfig">Import config</sys:String>
<sys:String x:Key="Instance.Export.Advanced.Modrinth">Modrinth upload mode</sys:String>
<sys:String x:Key="Instance.Export.Advanced.Modrinth.ToolTip">Enable this if you plan to upload to Modrinth to generate fully Modrinth-compliant modpack files.&#xA;Due to Modrinth requirements, resources only available from CurseForge cannot be downloaded online and will be bundled directly.&#xA;Additionally, the PCL CE launcher files cannot be included in the modpack when this is enabled.</sys:String>
<sys:String x:Key="Instance.Export.Advanced.Title">Advanced options</sys:String>
<!-- Instance.Export.Config -->
<sys:String x:Key="Instance.Export.Config.FileFilter">Modpack export config (*.txt)|*.txt</sys:String>
<sys:String x:Key="Instance.Export.Config.FileNotFound">File specified in config not found: {0}</sys:String>
<sys:String x:Key="Instance.Export.Config.FolderNotFound">Folder specified in config not found: {0}</sys:String>
<sys:String x:Key="Instance.Export.Config.Invalid">Config file is invalid or empty!</sys:String>
<sys:String x:Key="Instance.Export.Config.ShaderConfigSuffix">(Shader config)</sys:String>
<!-- Instance.Export.Config.Comment -->
<sys:String x:Key="Instance.Export.Config.Comment.BundleFiles">Whether to place Mods, resource packs, and shader packs directly into the modpack so they don't need to be downloaded during import.</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.BundleFiles2">Only consider this when you cannot reliably connect to CurseForge or Modrinth.</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.BundleFiles3">Redistribution may violate usage terms. Please avoid publicly distributing modpacks with bundled resource files!</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.ExcludedFiles">Excluded files</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.ExtraFiles">If you want to automatically place additional files in the root of the archive, write their paths below.</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.ExtraFiles2">Must be absolute paths. In each line, ending with \\ indicates a folder; not ending with \\ indicates a file.</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.IncludeLauncher">Whether to bundle the stable version of PCL CE so players without a launcher can install the modpack.</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.IncludeLauncherCustom">Whether to bundle PCL CE custom content, such as feature toggle settings, homepage, background music and images, etc.</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.ModifyRules">Modify the rules below to control the content to be exported.</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.Modrinth">If you plan to upload to Modrinth, enabling this will generate fully Modrinth-compliant modpack files.</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.Modrinth2">Due to Modrinth requirements, resources only available from CurseForge cannot be downloaded online and will be bundled directly.</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.Modrinth3">This option conflicts with IncludeLauncher, IncludeLauncherCustom, and DontCheckHostedAssets.</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.PackPath">Storage location for the exported file.</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.PackPath2">If set, the file will be placed directly at this path during export without prompting for a location.</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.PackPath3">If IncludeLauncher is True, it should end with .zip; if False, it should end with .mrpack.</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.ReverseMatch">Prefix with ! to invert. You can use *, ?, [] wildcards. Later lines override earlier ones.</sys:String>
<!-- Instance.Export.Option -->
<sys:String x:Key="Instance.Export.Option.DisabledMod">Disabled mods</sys:String>
<sys:String x:Key="Instance.Export.Option.Emi">EMI profile</sys:String>
<sys:String x:Key="Instance.Export.Option.Emi.Desc">Item favorites, default recipes, recipe history, etc.</sys:String>
<sys:String x:Key="Instance.Export.Option.Game">Game</sys:String>
<sys:String x:Key="Instance.Export.Option.GameProfile">Game profile</sys:String>
<sys:String x:Key="Instance.Export.Option.GameProfile.Desc">Command history, saved hotbar</sys:String>
<sys:String x:Key="Instance.Export.Option.GameSettings">Game settings</sys:String>
<sys:String x:Key="Instance.Export.Option.GameSettings.Desc">Key bindings, volume, video settings, etc.</sys:String>
<sys:String x:Key="Instance.Export.Option.ImportantData">Important modpack data</sys:String>
<sys:String x:Key="Instance.Export.Option.ImportantData.Desc">Scripts, built-in resource packs, data packs, etc.</sys:String>
<sys:String x:Key="Instance.Export.Option.Jei">JEI profile</sys:String>
<sys:String x:Key="Instance.Export.Option.Jei.Desc">Item favorites, etc.</sys:String>
<sys:String x:Key="Instance.Export.Option.Launcher">PCL CE Launcher</sys:String>
<sys:String x:Key="Instance.Export.Option.Launcher.Desc">Bundle PCL CE for players without a launcher</sys:String>
<sys:String x:Key="Instance.Export.Option.LauncherCustom">PCL CE custom content</sys:String>
<sys:String x:Key="Instance.Export.Option.LauncherCustom.Desc">Feature toggle settings, homepage, background music and images, etc.</sys:String>
<sys:String x:Key="Instance.Export.Option.Maps">Saved maps</sys:String>
<sys:String x:Key="Instance.Export.Option.Maps.Desc">Maps, waypoints, etc. saved by map mods</sys:String>
<sys:String x:Key="Instance.Export.Option.Mod">Mod</sys:String>
<sys:String x:Key="Instance.Export.Option.Mod.Desc">Mod files</sys:String>
<sys:String x:Key="Instance.Export.Option.ModSettings">Mod settings</sys:String>
<sys:String x:Key="Instance.Export.Option.OptiFine">OptiFine settings</sys:String>
<sys:String x:Key="Instance.Export.Option.Patchouli">Patchouli profile</sys:String>
<sys:String x:Key="Instance.Export.Option.Patchouli.Desc">Read records, bookmarks, reading history, etc.</sys:String>
<sys:String x:Key="Instance.Export.Option.Replay">Replay recordings</sys:String>
<sys:String x:Key="Instance.Export.Option.Replay.Desc">Replay mod recordings</sys:String>
<sys:String x:Key="Instance.Export.Option.ResourcePacks">Resource packs</sys:String>
<sys:String x:Key="Instance.Export.Option.ResourcePacks.Desc">Texture packs</sys:String>
<sys:String x:Key="Instance.Export.Option.Saves">Singleplayer saves</sys:String>
<sys:String x:Key="Instance.Export.Option.Saves.Desc">Worlds / maps</sys:String>
<sys:String x:Key="Instance.Export.Option.Screenshots">Screenshots</sys:String>
<sys:String x:Key="Instance.Export.Option.OtherFolders">Other folders</sys:String>
<sys:String x:Key="Instance.Export.Option.OtherFolders.Desc">Other folders not covered by the options above</sys:String>
<sys:String x:Key="Instance.Export.Option.Servers">Multiplayer server list</sys:String>
<sys:String x:Key="Instance.Export.Option.ShaderPacks">Shader packs</sys:String>
<sys:String x:Key="Instance.Export.Option.Structures">Exported structures</sys:String>
<sys:String x:Key="Instance.Export.Option.Structures.Desc">schematics folder</sys:String>
<!-- Instance.Left -->
<sys:String x:Key="Instance.Left.Export">Export</sys:String>
<sys:String x:Key="Instance.Left.Game">Game</sys:String>
<sys:String x:Key="Instance.Left.InitializeSettings.ConfirmMessage">Do you want to initialize per-instance settings for this instance? This action cannot be undone.</sys:String>
<sys:String x:Key="Instance.Left.InitializeSettings.ConfirmTitle">Initialize confirmation</sys:String>
<sys:String x:Key="Instance.Left.Install">Modify</sys:String>
<sys:String x:Key="Instance.Left.Mod">Mods</sys:String>
<sys:String x:Key="Instance.Left.Overview">Overview</sys:String>
<sys:String x:Key="Instance.Left.Refreshing">Refreshing...</sys:String>
<sys:String x:Key="Instance.Left.ResourcePacks">Resource packs</sys:String>
<sys:String x:Key="Instance.Left.Resources">Game resources</sys:String>
<sys:String x:Key="Instance.Left.Saves">Saves</sys:String>
<sys:String x:Key="Instance.Left.Schematics">Schematics</sys:String>
<sys:String x:Key="Instance.Left.Screenshots">Screenshots</sys:String>
<sys:String x:Key="Instance.Left.Servers">Servers</sys:String>
<sys:String x:Key="Instance.Left.Settings">Settings</sys:String>
<sys:String x:Key="Instance.Left.Shaders">Shaders</sys:String>
<!-- Instance.Overall -->
<sys:String x:Key="Instance.Overall.Category.AprilFools">April Fools'</sys:String>
<sys:String x:Key="Instance.Overall.Category.Hidden">Hidden</sys:String>
<sys:String x:Key="Instance.Overall.Category.Hidden.ToolTip">This instance will not be shown in the instance list by default.&#xA;Press F11 in the instance list page to view all hidden instances.</sys:String>
<sys:String x:Key="Instance.Overall.Category.LessUsed">Less-used</sys:String>
<sys:String x:Key="Instance.Overall.Category.Modable">Mod-supported</sys:String>
<sys:String x:Key="Instance.Overall.Category.Regular">Regular</sys:String>
<sys:String x:Key="Instance.Overall.Category.Title">Category</sys:String>
<sys:String x:Key="Instance.Overall.Delete.ConfirmMessage">Are you sure you want to delete instance {0}?</sys:String>
<sys:String x:Key="Instance.Overall.Delete.ConfirmMessageIsolated">Are you sure you want to delete instance {0}?&#xA;Since instance isolation is enabled for this instance, the corresponding saves, resource packs, mods, etc. will also be deleted!</sys:String>
<sys:String x:Key="Instance.Overall.Delete.ConfirmMessageIsolatedPermanent">Are you sure you want to permanently delete instance {0}?&#xA;Since instance isolation is enabled for this instance, the corresponding saves, resource packs, mods, etc. will also be deleted!</sys:String>
<sys:String x:Key="Instance.Overall.Delete.ConfirmMessagePermanent">Are you sure you want to permanently delete instance {0}?</sys:String>
<sys:String x:Key="Instance.Overall.Delete.ConfirmTitle">Delete instance confirmation</sys:String>
<sys:String x:Key="Instance.Overall.Delete.PermanentSuccess">Instance {0} has been permanently deleted!</sys:String>
<sys:String x:Key="Instance.Overall.Delete.RecycleBinSuccess">Instance {0} has been moved to the recycle bin!</sys:String>
<sys:String x:Key="Instance.Overall.Description.Default">Default description</sys:String>
<sys:String x:Key="Instance.Overall.Description.EditMessage">Edit the description text for this instance. Leave empty to use PCL CE's default description.</sys:String>
<sys:String x:Key="Instance.Overall.Description.EditTitle">Edit description</sys:String>
<sys:String x:Key="Instance.Overall.EditDescription">Edit description</sys:String>
<sys:String x:Key="Instance.Overall.Error.OperationFailed">An instance overview operation failed. See the details below.</sys:String>
<sys:String x:Key="Instance.Overall.Favorite">Favorite</sys:String>
<sys:String x:Key="Instance.Overall.Hide.ConfirmMessage">Are you sure you want to hide this instance from the instance list? After hiding it will no longer appear in the displayed instance list.&#xA;Press F11 in the instance list page to view hidden instances.</sys:String>
<sys:String x:Key="Instance.Overall.Hide.ConfirmTitle">Hide instance</sys:String>
<sys:String x:Key="Instance.Overall.Name.EditTitle">Rename instance</sys:String>
<sys:String x:Key="Instance.Overall.Name.RenameSuccess">Renamed successfully!</sys:String>
<sys:String x:Key="Instance.Overall.Patch.ConfirmMessage">Are you sure you want to patch the core of {0}?&#xA;Patching the game core may cause crashes or other issues.&#xA;After patching, file verification will be automatically disabled.</sys:String>
<sys:String x:Key="Instance.Overall.Patch.ConfirmTitle">Patching notice</sys:String>
<sys:String x:Key="Instance.Overall.Patch.Patching">Patching game core, this may take a while</sys:String>
<sys:String x:Key="Instance.Overall.Patch.SelectFile.Filter">Archive files(*.jar;*.zip)|*.jar;*.zip</sys:String>
<sys:String x:Key="Instance.Overall.Patch.SelectFile.Title">Select file for core patching</sys:String>
<sys:String x:Key="Instance.Overall.Patch.Success">Game core patched successfully</sys:String>
<sys:String x:Key="Instance.Overall.Personalization.Title">Personalization</sys:String>
<sys:String x:Key="Instance.Overall.Rename">Rename instance</sys:String>
<sys:String x:Key="Instance.Overall.Reset.ConfirmMessage">Are you sure you want to reset instance {0}?&#xA;PCL CE will try to re-download this instance's resource file information from the internet and perform automatic installation.</sys:String>
<sys:String x:Key="Instance.Overall.Reset.ConfirmTitle">Reset instance confirmation</sys:String>
<sys:String x:Key="Instance.Overall.Reset.NotSupported">This instance does not support reset yet!</sys:String>
<sys:String x:Key="Instance.Overall.Reset.QuiltUnsupported">Cannot reset: Quilt is no longer supported by this launcher.</sys:String>
<sys:String x:Key="Instance.Overall.Script.ExportFailed">Export launch script failed</sys:String>
<sys:String x:Key="Instance.Overall.Script.Exporting">Exporting launch script...</sys:String>
<sys:String x:Key="Instance.Overall.Script.ExportingWarning">Exporting launch script... (Note: using the script to launch may cause your login session to expire!)</sys:String>
<sys:String x:Key="Instance.Overall.Script.FileFilter">Batch file (*.bat)|*.bat</sys:String>
<sys:String x:Key="Instance.Overall.Script.SelectSaveTitle">Select where to save the script</sys:String>
<sys:String x:Key="Instance.Overall.Script.WaitForLaunchTask">Please wait for the current launch task to finish first!</sys:String>
<sys:String x:Key="Instance.Overall.Shortcuts.InstanceFolder">Instance folder</sys:String>
<sys:String x:Key="Instance.Overall.Shortcuts.ModsFolder">Mods folder</sys:String>
<sys:String x:Key="Instance.Overall.Shortcuts.SavesFolder">Saves folder</sys:String>
<sys:String x:Key="Instance.Overall.Shortcuts.Title">Shortcuts</sys:String>
<sys:String x:Key="Instance.Overall.Unfavorite">Unfavorite</sys:String>
<!-- Instance.Overall.Icon -->
<sys:String x:Key="Instance.Overall.Icon.Anvil">Anvil</sys:String>
<sys:String x:Key="Instance.Overall.Icon.Cleanroom">Cleanroom</sys:String>
<sys:String x:Key="Instance.Overall.Icon.Cobblestone">Cobblestone</sys:String>
<sys:String x:Key="Instance.Overall.Icon.CommandBlock">Command Block</sys:String>
<sys:String x:Key="Instance.Overall.Icon.DirtPath">Dirt Path</sys:String>
<sys:String x:Key="Instance.Overall.Icon.Egg">Egg</sys:String>
<sys:String x:Key="Instance.Overall.Icon.Fabric">Fabric (Fabric)</sys:String>
<sys:String x:Key="Instance.Overall.Icon.GoldBlock">Gold Block</sys:String>
<sys:String x:Key="Instance.Overall.Icon.GrassBlock">Grass Block</sys:String>
<sys:String x:Key="Instance.Overall.Icon.NeoForgeFox">Fox (NeoForge)</sys:String>
<sys:String x:Key="Instance.Overall.Icon.Quilt">Patch (Quilt)</sys:String>
<sys:String x:Key="Instance.Overall.Icon.RedstoneBlock">Redstone Block</sys:String>
<sys:String x:Key="Instance.Overall.Icon.RedstoneLampOff">Redstone Lamp (off)</sys:String>
<sys:String x:Key="Instance.Overall.Icon.RedstoneLampOn">Redstone Lamp (on)</sys:String>
<sys:String x:Key="Instance.Overall.Icon.SelectFile.Filter">Common image files(*.png;*.jpg;*.gif)|*.png;*.jpg;*.gif</sys:String>
<sys:String x:Key="Instance.Overall.Icon.SelectFile.Title">Select image</sys:String>
<sys:String x:Key="Instance.Overall.Icon.Title">Icon</sys:String>
<!-- Instance.Overall.Info -->
<sys:String x:Key="Instance.Overall.Info.Installed">Installed</sys:String>
<sys:String x:Key="Instance.Overall.Info.LaunchCount.Count">Launched {0} times</sys:String>
<sys:String x:Key="Instance.Overall.Info.LaunchCount.Never">Never launched</sys:String>
<sys:String x:Key="Instance.Overall.Info.LaunchCount.Title">Times launched</sys:String>
<sys:String x:Key="Instance.Overall.Info.LoadInstanceInfoTask">Loading instance info</sys:String>
<sys:String x:Key="Instance.Overall.Info.LoadModpackInfoTask">Loading modpack info</sys:String>
<sys:String x:Key="Instance.Overall.Info.Loading">Loading information...</sys:String>
<sys:String x:Key="Instance.Overall.Info.ModpackVersion">Modpack version</sys:String>
<sys:String x:Key="Instance.Overall.Info.Title">Properties</sys:String>
<!-- Instance.Overall.Manage -->
<sys:String x:Key="Instance.Overall.Manage.Delete">Delete instance</sys:String>
<sys:String x:Key="Instance.Overall.Manage.ExportLaunchScript">Export launch script</sys:String>
<sys:String x:Key="Instance.Overall.Manage.PatchCore">Patch core</sys:String>
<sys:String x:Key="Instance.Overall.Manage.Reinstall.ToolTip">Re-download installation information for this instance from the internet and perform automatic installation.&#xA;Your saves, mods, resource packs, etc. will not be lost.&#xA;If the game cannot start due to missing Libraries or other resources, try this option.</sys:String>
<sys:String x:Key="Instance.Overall.Manage.RepairFiles">Repair files</sys:String>
<sys:String x:Key="Instance.Overall.Manage.RepairFiles.ToolTip">Check whether the dependencies of the instance are intact, and re-download the missing or corrupted index and assets files.&#xA;If you encounter missing sounds in game, try this option.</sys:String>
<sys:String x:Key="Instance.Overall.Manage.TestGame">Test game</sys:String>
<sys:String x:Key="Instance.Overall.Manage.Title">Advanced options</sys:String>
<!-- Instance.Overall.Repair -->
<sys:String x:Key="Instance.Overall.Repair.Cancelled.WithTaskName">{0} canceled!</sys:String>
<sys:String x:Key="Instance.Overall.Repair.DisableVerificationHint">Please first disable [Instance settings → Settings → Advanced startup options → Disable file verification], then try repairing the files!</sys:String>
<sys:String x:Key="Instance.Overall.Repair.Failed">Failed: </sys:String>
<sys:String x:Key="Instance.Overall.Repair.Failed.WithDetail">{0} failed.&#xA;&#xA;Details:&#xA;{1}</sys:String>
<sys:String x:Key="Instance.Overall.Repair.Processing">Processing, please wait...</sys:String>
<sys:String x:Key="Instance.Overall.Repair.Success">Success!</sys:String>
<sys:String x:Key="Instance.Overall.Repair.Success.WithTaskName">{0} completed successfully!</sys:String>
<sys:String x:Key="Instance.Overall.Repair.TaskName">File repair</sys:String>
<!-- Instance.Resource -->
<sys:String x:Key="Instance.Resource.BackToParent">Back to parent</sys:String>
<sys:String x:Key="Instance.Resource.CancelSelection">Cancel</sys:String>
<sys:String x:Key="Instance.Resource.CheckMods">Check mods</sys:String>
<sys:String x:Key="Instance.Resource.CheckMods.ToolTip">Quickly check mod version requirements, duplicates, missing dependencies, etc.&#xA;Results are for reference only and may be inaccurate due to incorrect information in some mods.</sys:String>
<sys:String x:Key="Instance.Resource.Datapack.DownloadNew">Download new data packs</sys:String>
<sys:String x:Key="Instance.Resource.Datapack.Empty.Description">You can install data packs from previously downloaded files.&#xA;Data packs need to be placed in the save's datapacks folder to take effect.</sys:String>
<sys:String x:Key="Instance.Resource.Datapack.Empty.Title">No data packs installed</sys:String>
<sys:String x:Key="Instance.Resource.Datapack.ExportInfo.ToolTip">Export detailed data pack info</sys:String>
<sys:String x:Key="Instance.Resource.Datapack.Loading">Loading data pack list</sys:String>
<sys:String x:Key="Instance.Resource.Delete.Failed">Deletion failed due to file being in use. Please try closing any running games and try again!</sys:String>
<sys:String x:Key="Instance.Resource.Delete.PermanentMultiple">Permanently deleted {0} items!</sys:String>
<sys:String x:Key="Instance.Resource.Delete.PermanentSingle">Permanently deleted {0}!</sys:String>
<sys:String x:Key="Instance.Resource.Delete.RecycleMultiple">Moved {0} items to the recycle bin!</sys:String>
<sys:String x:Key="Instance.Resource.Delete.RecycleSingle">Moved {0} to the recycle bin!</sys:String>
<sys:String x:Key="Instance.Resource.Disable">Disable</sys:String>
<sys:String x:Key="Instance.Resource.DownloadNew">Download new</sys:String>
<sys:String x:Key="Instance.Resource.Ed.FileConflict">File conflict</sys:String>
<sys:String x:Key="Instance.Resource.Ed.FileConflict.Message">There are currently two mod files (enabled and disabled):&#xA; - {0}&#xA; - {1}&#xA;&#xA;These two files have different content.&#xA;You must manually delete or rename one of them before proceeding.</sys:String>
<sys:String x:Key="Instance.Resource.Ed.ToggleFailed">Mod status toggle failed. Please try closing any running games and try again!</sys:String>
<sys:String x:Key="Instance.Resource.Empty.Description">You can install resources from previously downloaded files.&#xA;If you have already installed resources, the instance isolation setting may be incorrect. Please adjust it in the settings.</sys:String>
<sys:String x:Key="Instance.Resource.Empty.DescriptionWithDownload">You can download new resources or install from previously downloaded files.&#xA;If you have already installed resources, the instance isolation setting may be incorrect. Please adjust it in the settings.</sys:String>
<sys:String x:Key="Instance.Resource.Empty.Title">No resources installed</sys:String>
<sys:String x:Key="Instance.Resource.EmptyFolder.Description">You can install resources from previously downloaded files</sys:String>
<sys:String x:Key="Instance.Resource.EmptyFolder.Title">This folder is empty</sys:String>
<sys:String x:Key="Instance.Resource.Enable">Enable</sys:String>
<sys:String x:Key="Instance.Resource.Error.OperationFailed">A resource management operation failed. See the details below.</sys:String>
<sys:String x:Key="Instance.Resource.Export.FilesFilter">Text file (*.txt)|*.txt|CSV file (*.csv)|*.csv</sys:String>
<sys:String x:Key="Instance.Resource.Export.Mode.Csv">CSV format</sys:String>
<sys:String x:Key="Instance.Resource.Export.Mode.Message">TXT format: Only export the current resource file name info, usually sufficient for others to get installed resource info&#xA;CSV format: Export detailed resource info, including file name, project ID, version info, and other details</sys:String>
<sys:String x:Key="Instance.Resource.Export.Mode.Title">Select export mode</sys:String>
<sys:String x:Key="Instance.Resource.Export.Mode.Txt">TXT format</sys:String>
<sys:String x:Key="Instance.Resource.Export.SelectSaveLocation">Select save location</sys:String>
<sys:String x:Key="Instance.Resource.ExportInfo">Export info</sys:String>
<sys:String x:Key="Instance.Resource.ExportInfo.ToolTip">Export detailed version info for resources</sys:String>
<sys:String x:Key="Instance.Resource.Favorite">Favorite</sys:String>
<sys:String x:Key="Instance.Resource.InstallFromFiles">Install from files</sys:String>
<sys:String x:Key="Instance.Resource.List.Loading">Loading resource list</sys:String>
<sys:String x:Key="Instance.Resource.Loading">Loading resource list</sys:String>
<sys:String x:Key="Instance.Resource.Mod.Disabled.GoDownload">Go to the download page</sys:String>
<sys:String x:Key="Instance.Resource.Mod.Disabled.Message">You need to install a mod loader such as Forge, Fabric, etc. to use mods. Please go to the download page to install one.&#xA;If you have already installed a mod loader, you may have selected the wrong instance. Please switch to the correct instance.</sys:String>
<sys:String x:Key="Instance.Resource.Mod.Disabled.SelectInstance">Select an instance</sys:String>
<sys:String x:Key="Instance.Resource.Mod.Disabled.Title">This instance cannot install mods</sys:String>
<sys:String x:Key="Instance.Resource.Schematic.DownloadMod">Download Litematica</sys:String>
<sys:String x:Key="Instance.Resource.Schematic.Unavailable.Message">You may need to install the Litematica mod first. If you have already installed one, please launch the game once.&#xA;Alternatively, you may have selected the wrong instance. Please use the instance selection button to switch instances.</sys:String>
<sys:String x:Key="Instance.Resource.Schematic.Unavailable.Title">Schematics unavailable for this instance</sys:String>
<sys:String x:Key="Instance.Resource.Search.Hint">Search resources: name / description / tags</sys:String>
<sys:String x:Key="Instance.Resource.SelectAll">Select all</sys:String>
<sys:String x:Key="Instance.Resource.SelectedCount">{0} file(s) selected</sys:String>
<sys:String x:Key="Instance.Resource.ShareSelected">Share</sys:String>
<sys:String x:Key="Instance.Resource.Sort.AddTime">Date added</sys:String>
<sys:String x:Key="Instance.Resource.Sort.FileName">File name</sys:String>
<sys:String x:Key="Instance.Resource.Sort.FileSize">File size</sys:String>
<sys:String x:Key="Instance.Resource.Sort.ResourceName">Resource name</sys:String>
<sys:String x:Key="Instance.Resource.Sort.TagCount">Tag count</sys:String>
<sys:String x:Key="Instance.Resource.Sort.Text">Sort: {0}</sys:String>
<!-- Instance.Resource.Filter -->
<sys:String x:Key="Instance.Resource.Filter.All">All</sys:String>
<sys:String x:Key="Instance.Resource.Filter.AllWithCount">All ({0})</sys:String>
<sys:String x:Key="Instance.Resource.Filter.Disabled">Disabled</sys:String>
<sys:String x:Key="Instance.Resource.Filter.DisabledWithCount">Disabled ({0})</sys:String>
<sys:String x:Key="Instance.Resource.Filter.Duplicate">Duplicate</sys:String>
<sys:String x:Key="Instance.Resource.Filter.DuplicateWithCount">Duplicate ({0})</sys:String>
<sys:String x:Key="Instance.Resource.Filter.Enabled">Enabled</sys:String>
<sys:String x:Key="Instance.Resource.Filter.EnabledWithCount">Enabled ({0})</sys:String>
<sys:String x:Key="Instance.Resource.Filter.Error">Error</sys:String>
<sys:String x:Key="Instance.Resource.Filter.ErrorWithCount">Error ({0})</sys:String>
<sys:String x:Key="Instance.Resource.Filter.SearchResult">Search results</sys:String>
<sys:String x:Key="Instance.Resource.Filter.Updatable">Updatable</sys:String>
<sys:String x:Key="Instance.Resource.Filter.UpdatableWithCount">Updatable ({0})</sys:String>
<!-- Instance.Resource.Install -->
<sys:String x:Key="Instance.Resource.Install.GenericConfirm.Message">Do you want to install these files as {0} to {1}?</sys:String>
<sys:String x:Key="Instance.Resource.Install.GenericConfirm.Title">Confirm {0} installation</sys:String>
<sys:String x:Key="Instance.Resource.Install.ModConfirm.Message">Do you want to install these files as a Mod to {0}?</sys:String>
<sys:String x:Key="Instance.Resource.Install.ModConfirm.Title">Confirm mod installation</sys:String>
<sys:String x:Key="Instance.Resource.Install.OverwriteConfirm.Message">A file with the name {0} already exists. Overwrite?</sys:String>
<sys:String x:Key="Instance.Resource.Install.OverwriteConfirm.Title">Confirm file overwrite</sys:String>
<sys:String x:Key="Instance.Resource.Install.RestoreFromRecycleBin">Please restore the file from the recycle bin first before trying to install!</sys:String>
<sys:String x:Key="Instance.Resource.Install.SelectModableInstance">To install a mod, please first select a mod-compatible instance!</sys:String>
<sys:String x:Key="Instance.Resource.Install.SuccessMultiple">Successfully installed {0} {1}(s)!</sys:String>
<sys:String x:Key="Instance.Resource.Install.SuccessSingle">Successfully installed {0}!</sys:String>
<sys:String x:Key="Instance.Resource.Install.UnsupportedFormat">Unsupported file format: {0}. {1} supported formats: {2}</sys:String>
<!-- Instance.Resource.Install.FileDialog -->
<sys:String x:Key="Instance.Resource.Install.FileDialog.Mod.Filter">Mod files (*.jar;*.litemod;*.disabled;*.old)|*.jar;*.litemod;*.disabled;*.old</sys:String>
<sys:String x:Key="Instance.Resource.Install.FileDialog.Mod.Title">Select mods to install</sys:String>
<sys:String x:Key="Instance.Resource.Install.FileDialog.ResourcePack.Filter">Resource pack files (*.zip)|*.zip</sys:String>
<sys:String x:Key="Instance.Resource.Install.FileDialog.ResourcePack.Title">Select resource packs to install</sys:String>
<sys:String x:Key="Instance.Resource.Install.FileDialog.Schematic.Filter">Schematic files (*.litematic;*.nbt;*.schematic;*.schem)|*.litematic;*.nbt;*.schematic;*.schem</sys:String>
<sys:String x:Key="Instance.Resource.Install.FileDialog.Schematic.Title">Select schematics to install</sys:String>
<sys:String x:Key="Instance.Resource.Install.FileDialog.Shader.Filter">Shader pack files (*.zip)|*.zip</sys:String>
<sys:String x:Key="Instance.Resource.Install.FileDialog.Shader.Title">Select shader packs to install</sys:String>
<!-- Instance.Resource.Item -->
<sys:String x:Key="Instance.Resource.Item.ErrorSuffix"> [Error]</sys:String>
<sys:String x:Key="Instance.Resource.Item.FolderTag">Folder</sys:String>
<sys:String x:Key="Instance.Resource.Item.InfoUnavailable">Unable to get information</sys:String>
<sys:String x:Key="Instance.Resource.Item.OpenChangelogFailed">Failed to open changelog</sys:String>
<sys:String x:Key="Instance.Resource.Item.Update">Update</sys:String>
<sys:String x:Key="Instance.Resource.Item.UpdateCompare">Current version: {0} ({1})&#xA;Latest version: {2} ({3})</sys:String>
<sys:String x:Key="Instance.Resource.Item.UpdateConfirm.Message">Update {0}?</sys:String>
<sys:String x:Key="Instance.Resource.Item.UpdateConfirm.Title">Confirm update</sys:String>
<sys:String x:Key="Instance.Resource.Item.UpdateToolTip">Click to update, right-click to view changelog.</sys:String>
<sys:String x:Key="Instance.Resource.Item.ViewChangelog">View changelog</sys:String>
<!-- Instance.Resource.Item.Info -->
<sys:String x:Key="Instance.Resource.Item.Info.Author">Author: {0}</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.ContainsMany">Contains {0} files</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.ContainsOne">Contains 1 file</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.Dependency">Depends on: </sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.DependencyVersion">{0}, version: {1}</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.DetailedError">Detailed error: </sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.EmptyFolder">Empty folder</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.FailedMessage">Unable to read information for this resource.</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.FailedMessage.WithDetail">Unable to read information for this resource.&#xA;&#xA;Details:&#xA;{0}</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.FailedTitle">Failed to Read Resource</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.File">File: {0} ({1})</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.FolderNotFound">Folder not found</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.LoadingDetail">Loading details...</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.McMod">Search on MCMod</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.ModId">Mod ID: {0}</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.OpenWebsite">Open website</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.Path">Path: {0}</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.Return">Return</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.Version">Version: {0}</sys:String>
<!-- Instance.Resource.Item.Schematic -->
<sys:String x:Key="Instance.Resource.Item.Schematic.CreatedTime">Created: </sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.DataVersion">Data version: </sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.DetailInfo">Details: </sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.EnclosingDimensions">Enclosing dimensions: </sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.EnclosingSize">Enclosing size: </sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.FileType">File type: </sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.FileType.Mcedit">MCEdit/WorldEdit schematic</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.FileType.Nbt">Vanilla structure</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.FileType.Sponge">Sponge schematic</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.GameVersion">Game version: </sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.ModifiedTime">Modified: </sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.OriginalName">Original name: </sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.RegionCount">Region count: </sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.Size">Size: </sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.SpongeVersion">Sponge version: </sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.TotalBlocks">Total blocks: </sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.TotalVolume">Total volume: </sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.Version">Schematic version: </sys:String>
<!-- Instance.Resource.Update -->
<sys:String x:Key="Instance.Resource.Update">Update</sys:String>
<sys:String x:Key="Instance.Resource.Update.Aborted">Resource update aborted!</sys:String>
<sys:String x:Key="Instance.Resource.Update.Failed">Resource update failed: {0}</sys:String>
<sys:String x:Key="Instance.Resource.Update.SuccessMultiple">Successfully updated {0} resources!</sys:String>
<sys:String x:Key="Instance.Resource.Update.SuccessSingle">Successfully updated {0}!</sys:String>
<sys:String x:Key="Instance.Resource.Update.Task.DownloadFiles">Download updated resource files</sys:String>
<sys:String x:Key="Instance.Resource.Update.Task.ReplaceFiles">Replace old resource files</sys:String>
<sys:String x:Key="Instance.Resource.Update.Task.Title">Update resources: {0}</sys:String>
<sys:String x:Key="Instance.Resource.Update.WaitingForUpdate">Updating resources</sys:String>
<sys:String x:Key="Instance.Resource.Update.Warning.Confirm">I understand the risks, continue updating</sys:String>
<sys:String x:Key="Instance.Resource.Update.Warning.Message">Newer versions of mods may be incompatible with old saves or other mods, which could cause game crashes or even permanently corrupt your saves!&#xA;If you are playing a modpack, do NOT update mods on your own!&#xA;&#xA;Before updating, please back up your saves and check the mod's changelog.&#xA;If problems arise after updating, you can retrieve the previous mod version from the recycle bin.</sys:String>
<sys:String x:Key="Instance.Resource.Update.Warning.Title">Mod update warning</sys:String>
<!-- Instance.Saves -->
<sys:String x:Key="Instance.Saves.CopiedToClipboard">Save folder copied to clipboard!</sys:String>
<sys:String x:Key="Instance.Saves.CopyFailed">Copy failed...</sys:String>
<sys:String x:Key="Instance.Saves.CopyPasteWarning">Warning! Deleting before pasting will cause save loss!</sys:String>
<sys:String x:Key="Instance.Saves.CopySave">Copy save</sys:String>
<sys:String x:Key="Instance.Saves.CreationTime">Created: {0}, last modified: {1}</sys:String>
<sys:String x:Key="Instance.Saves.DeleteFailed">Failed to delete save!</sys:String>
<sys:String x:Key="Instance.Saves.DeletedToRecycleBin">Save moved to recycle bin!</sys:String>
<sys:String x:Key="Instance.Saves.Deleting">Deleting...</sys:String>
<sys:String x:Key="Instance.Saves.Detail">Details</sys:String>
<sys:String x:Key="Instance.Saves.Details">Details</sys:String>
<sys:String x:Key="Instance.Saves.DuplicateFolder">Found duplicate folder, cannot paste: {0}</sys:String>
<sys:String x:Key="Instance.Saves.Empty.Message">You can view the current instance's saves here</sys:String>
<sys:String x:Key="Instance.Saves.Empty.Title">No saves yet</sys:String>
<sys:String x:Key="Instance.Saves.Error.OperationFailed">A saves operation failed. See the details below.</sys:String>
<sys:String x:Key="Instance.Saves.Folder.DoubleClickHint.Message">Double-click a folder to enter it.</sys:String>
<sys:String x:Key="Instance.Saves.Folder.DoubleClickHint.Title">Operation notice</sys:String>
<sys:String x:Key="Instance.Saves.Folder.NotFound">Folder does not exist or has been deleted</sys:String>
<sys:String x:Key="Instance.Saves.FolderNotFound">Save folder not found!</sys:String>
<sys:String x:Key="Instance.Saves.Left.Datapack">Data pack</sys:String>
<sys:String x:Key="Instance.Saves.Left.Info">Info</sys:String>
<sys:String x:Key="Instance.Saves.Left.QuickActions">Quick actions</sys:String>
<sys:String x:Key="Instance.Saves.Left.Refreshing">Refreshing...</sys:String>
<sys:String x:Key="Instance.Saves.Left.SwitchFailed">Failed to switch sub-page (ID {0})</sys:String>
<sys:String x:Key="Instance.Saves.Left.UnknownSubPage">Unknown instance settings sub-page type: {0}</sys:String>
<sys:String x:Key="Instance.Saves.ListTitle">Save list</sys:String>
<sys:String x:Key="Instance.Saves.LoadListFailed">Failed to load save list</sys:String>
<sys:String x:Key="Instance.Saves.OpenFileLocation">Open file location</sys:String>
<sys:String x:Key="Instance.Saves.OpenFolder">Open saves folder</sys:String>
<sys:String x:Key="Instance.Saves.PasteFile">Paste clipboard file</sys:String>
<sys:String x:Key="Instance.Saves.PasteFolderFailed">Failed to paste save folder</sys:String>
<sys:String x:Key="Instance.Saves.PastedCount">Pasted {0} folder(s)</sys:String>
<sys:String x:Key="Instance.Saves.QuickActions">Quick actions</sys:String>
<sys:String x:Key="Instance.Saves.QuickPlay">Quick launch</sys:String>
<sys:String x:Key="Instance.Saves.RefreshUiFailed">Failed to refresh save UI</sys:String>
<sys:String x:Key="Instance.Saves.SaveListTitle">Save list ({0})</sys:String>
<sys:String x:Key="Instance.Saves.SearchError">Error occurred during search</sys:String>
<sys:String x:Key="Instance.Saves.SearchHint">Search save name</sys:String>
<sys:String x:Key="Instance.Saves.SearchResultTitle">Search results ({0})</sys:String>
<sys:String x:Key="Instance.Saves.SortBy">Sort by: {0}</sys:String>
<sys:String x:Key="Instance.Saves.SortCreateTime">Creation time</sys:String>
<sys:String x:Key="Instance.Saves.SortFileName">File name</sys:String>
<sys:String x:Key="Instance.Saves.SortModifyTime">Modification time</sys:String>
<sys:String x:Key="Instance.Saves.SourceNotFolder">Source folder does not exist or is not a folder</sys:String>
<sys:String x:Key="Instance.Saves.Status.Refreshing">Refreshing...</sys:String>
<!-- Instance.Saves.Datapack -->
<sys:String x:Key="Instance.Saves.Datapack.Delete.FileOccupied">Deletion failed because the file is in use. Try closing the running game first!</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Delete.PermanentMultiple">Permanently deleted {0} items!</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Delete.PermanentSingle">Permanently deleted {0}!</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Delete.RecycleMultiple">Moved {0} items to the recycle bin!</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Delete.RecycleSingle">Moved {0} to the recycle bin!</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Export.Mode.Message">TXT format: Only export the current data pack file name information&#xA;CSV format: Export detailed data pack information, including file name, project ID, version information, and other details</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Info.Author">Author: </sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Info.DatapackId">Data pack ID: </sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Info.File">File: </sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Info.ReadFailed">Unable to read data pack information.</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Info.ReadFailed.WithDetail">Unable to read data pack information.&#xA;&#xA;Details:&#xA;{0}</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Info.ReadFailedTitle">Data pack read failed</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Info.Version">Version: </sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Install.FileDialog.Filter">Data pack files (*.zip)|*.zip</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Install.FileDialog.Title">Select data packs to install</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Install.Message">Do you want to install these files as data packs to the current save?</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Install.Title">Confirm data pack installation</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Replace.FileNameConflict">A file with the same name already exists: {0}. Please handle that file first and try again.</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.ToggleWarning">Data pack status toggle failed. Please try closing any running games and try again!</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Update.Task.DownloadFiles">Download updated data pack files</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Update.Task.ReplaceFiles">Replace old data pack files</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Update.Task.Title">Update data packs: {0}</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Update.UnsafeFilesSkipped">Skipped {0} data pack updates with unsafe file names.</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Update.Warning.Confirm">I understand the risks, continue updating</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Update.Warning.Message">Newer versions of data packs may be incompatible with old saves or other data packs, which could cause game crashes or save corruption!&#xA;&#xA;Before updating, please back up your save.&#xA;If problems arise after updating, you can retrieve the previous data pack version from the recycle bin.</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Update.Warning.Title">Data pack update warning</sys:String>
<!-- Instance.Saves.Info -->
<sys:String x:Key="Instance.Saves.Info.AllowCommands">Allow commands</sys:String>
<sys:String x:Key="Instance.Saves.Info.AllowCommands.Allowed">Allowed</sys:String>
<sys:String x:Key="Instance.Saves.Info.AllowCommands.NotAllowed">Not allowed</sys:String>
<sys:String x:Key="Instance.Saves.Info.Chunkbase.PreviewVersion">Save version '{0}' may be a preview version, unsupported, cannot open in Chunkbase</sys:String>
<sys:String x:Key="Instance.Saves.Info.Chunkbase.ToolTip">Open in Chunkbase</sys:String>
<sys:String x:Key="Instance.Saves.Info.Chunkbase.UnknownVersion">Unable to determine save version, cannot open in Chunkbase</sys:String>
<sys:String x:Key="Instance.Saves.Info.Details.Title">Save details</sys:String>
<sys:String x:Key="Instance.Saves.Info.Difficulty.Easy">Easy</sys:String>
<sys:String x:Key="Instance.Saves.Info.Difficulty.Hard">Hard</sys:String>
<sys:String x:Key="Instance.Saves.Info.Difficulty.Normal">Normal</sys:String>
<sys:String x:Key="Instance.Saves.Info.Difficulty.Peaceful">Peaceful</sys:String>
<sys:String x:Key="Instance.Saves.Info.DifficultyLocked">Difficulty locked: {0}</sys:String>
<sys:String x:Key="Instance.Saves.Info.Error.ChunkbaseFailed">Failed to open Chunkbase</sys:String>
<sys:String x:Key="Instance.Saves.Info.Error.ClipboardFailed">Failed to copy to clipboard</sys:String>
<sys:String x:Key="Instance.Saves.Info.Error.LoadFailed">Failed to get save info</sys:String>
<sys:String x:Key="Instance.Saves.Info.GameDifficultyLabel">Game difficulty</sys:String>
<sys:String x:Key="Instance.Saves.Info.GameMode">Game mode</sys:String>
<sys:String x:Key="Instance.Saves.Info.GameMode.Adventure">Adventure</sys:String>
<sys:String x:Key="Instance.Saves.Info.GameMode.Creative">Creative</sys:String>
<sys:String x:Key="Instance.Saves.Info.GameMode.Hardcore">Hardcore</sys:String>
<sys:String x:Key="Instance.Saves.Info.GameMode.Spectator">Spectator</sys:String>
<sys:String x:Key="Instance.Saves.Info.GameMode.Survival">Survival</sys:String>
<sys:String x:Key="Instance.Saves.Info.GetFailed">Failed to get</sys:String>
<sys:String x:Key="Instance.Saves.Info.Hardness">Difficulty</sys:String>
<sys:String x:Key="Instance.Saves.Info.LastPlayed">Last played</sys:String>
<sys:String x:Key="Instance.Saves.Info.LevelName">Save name</sys:String>
<sys:String x:Key="Instance.Saves.Info.LockDifficulty">Lock difficulty</sys:String>
<sys:String x:Key="Instance.Saves.Info.LockDifficulty.ToolTip">Lock current difficulty, preventing changes in-game</sys:String>
<sys:String x:Key="Instance.Saves.Info.Modify.BeforeSave">Make sure the save is not open in-game before modifying settings</sys:String>
<sys:String x:Key="Instance.Saves.Info.Modify.CheatFailed">Failed to modify cheat settings</sys:String>
<sys:String x:Key="Instance.Saves.Info.Modify.CheatSuccess">Cheat settings modified successfully</sys:String>
<sys:String x:Key="Instance.Saves.Info.Modify.DifficultyFailed">Failed to modify difficulty settings</sys:String>
<sys:String x:Key="Instance.Saves.Info.Modify.DifficultySuccess">Difficulty modified successfully</sys:String>
<sys:String x:Key="Instance.Saves.Info.PlayTime">Play time</sys:String>
<sys:String x:Key="Instance.Saves.Info.Seed">Seed</sys:String>
<sys:String x:Key="Instance.Saves.Info.Settings.Title">Save settings</sys:String>
<sys:String x:Key="Instance.Saves.Info.SpawnPoint">Spawn point (X/Y/Z)</sys:String>
<sys:String x:Key="Instance.Saves.Info.Version">Save version</sys:String>
<sys:String x:Key="Instance.Saves.Info.VersionHint.1_3">Unable to get save version, difficulty, and cheat settings for versions below 1.3</sys:String>
<sys:String x:Key="Instance.Saves.Info.VersionHint.1_8">Unable to get save version and difficulty for versions below 1.8</sys:String>
<sys:String x:Key="Instance.Saves.Info.VersionHint.1_9">Unable to get save version for versions below 1.9</sys:String>
<!-- Instance.Screenshot -->
<sys:String x:Key="Instance.Screenshot.CopiedToClipboard">Screenshot copied to clipboard!</sys:String>
<sys:String x:Key="Instance.Screenshot.CopyFailed">Screenshot copy failed!</sys:String>
<sys:String x:Key="Instance.Screenshot.DeleteFailed">Failed to delete screenshot!</sys:String>
<sys:String x:Key="Instance.Screenshot.Deleted">Screenshot moved to recycle bin!</sys:String>
<sys:String x:Key="Instance.Screenshot.Empty.Message">Press the screenshot key (default F2) in-game to view saved screenshots here</sys:String>
<sys:String x:Key="Instance.Screenshot.Empty.Title">No screenshots yet</sys:String>
<sys:String x:Key="Instance.Screenshot.FileNotFound">Screenshot file not found!</sys:String>
<sys:String x:Key="Instance.Screenshot.OpenFailed">Failed to open screenshot!</sys:String>
<sys:String x:Key="Instance.Screenshot.OpenFolder">Open screenshots folder</sys:String>
<sys:String x:Key="Instance.Screenshot.QuickActions">Quick actions</sys:String>
<!-- Instance.Server -->
<sys:String x:Key="Instance.Server.AddServer">Add new server</sys:String>
<sys:String x:Key="Instance.Server.AddressPrompt">Please enter new server address:</sys:String>
<sys:String x:Key="Instance.Server.DefaultName">Minecraft Server</sys:String>
<sys:String x:Key="Instance.Server.EditTitle">Edit server info</sys:String>
<sys:String x:Key="Instance.Server.Empty.Message">No servers found. Add a server in-game or click the button below to add one.</sys:String>
<sys:String x:Key="Instance.Server.Empty.Title">No servers yet</sys:String>
<sys:String x:Key="Instance.Server.FoundServers">Server list found</sys:String>
<sys:String x:Key="Instance.Server.IndexNotFound">Unable to find server index in list</sys:String>
<sys:String x:Key="Instance.Server.NamePrompt">Please enter new server name:</sys:String>
<sys:String x:Key="Instance.Server.NoFrequentRefresh">Please do not refresh too frequently!</sys:String>
<sys:String x:Key="Instance.Server.NoServersFound">No servers found</sys:String>
<sys:String x:Key="Instance.Server.QuickActions">Quick actions</sys:String>
<sys:String x:Key="Instance.Server.ReadDataFailed">Unable to read server data file</sys:String>
<sys:String x:Key="Instance.Server.ReadFileFailed">Failed to read servers.dat file</sys:String>
<sys:String x:Key="Instance.Server.RefreshAll">Refresh all servers</sys:String>
<sys:String x:Key="Instance.Server.RefreshFailed">Failed to refresh server list</sys:String>
<sys:String x:Key="Instance.Server.RefreshFailed.WithDetail">Failed to refresh server list.&#xA;&#xA;Details:&#xA;{0}</sys:String>
<sys:String x:Key="Instance.Server.RefreshInfo">Refresh server info</sys:String>
<sys:String x:Key="Instance.Server.RefreshingList">Refreshing server list, please wait...</sys:String>
<sys:String x:Key="Instance.Server.Removed">Server removed</sys:String>
<sys:String x:Key="Instance.Server.Updated">Server info updated</sys:String>
<sys:String x:Key="Instance.Server.WriteDataFailed">Unable to write server data file</sys:String>
<!-- Instance.Server.Card -->
<sys:String x:Key="Instance.Server.Card.AddressCopied">Server address copied: {0}</sys:String>
<sys:String x:Key="Instance.Server.Card.Connecting">Connecting</sys:String>
<sys:String x:Key="Instance.Server.Card.ConnectingDots">Connecting...</sys:String>
<sys:String x:Key="Instance.Server.Card.ConnectingTo">Connecting to server {0}...</sys:String>
<sys:String x:Key="Instance.Server.Card.CopyAddress">Copy address</sys:String>
<sys:String x:Key="Instance.Server.Card.CopyAddressFailed">Failed to copy server address</sys:String>
<sys:String x:Key="Instance.Server.Card.Edit">Edit</sys:String>
<sys:String x:Key="Instance.Server.Card.EditFailed">Failed to edit server info: {0}</sys:String>
<sys:String x:Key="Instance.Server.Card.LaunchFailed">Failed to launch server</sys:String>
<sys:String x:Key="Instance.Server.Card.LaunchFailedMsg">Failed to launch server: {0}</sys:String>
<sys:String x:Key="Instance.Server.Card.Offline">Offline</sys:String>
<sys:String x:Key="Instance.Server.Card.RefreshingStatus">Refreshing server {0} status...</sys:String>
<sys:String x:Key="Instance.Server.Card.Remove">Remove</sys:String>
<sys:String x:Key="Instance.Server.Card.RemoveConfirmMessage">Are you sure you want to remove server {0}?&#xA;'{1}' will be removed from your list, including the in-game list, and cannot be recovered.</sys:String>
<sys:String x:Key="Instance.Server.Card.RemoveConfirmTitle">Confirm server removal</sys:String>
<sys:String x:Key="Instance.Server.Card.ServerOffline">Server offline</sys:String>
<!-- Instance.Setup -->
<sys:String x:Key="Instance.Setup.Error.OperationFailed">An instance settings operation failed. See the details below.</sys:String>
<sys:String x:Key="Instance.Setup.FollowGlobal">Follow global settings</sys:String>
<sys:String x:Key="Instance.Setup.Hint">These settings only apply to this game instance and do not affect other instances.</sys:String>
<sys:String x:Key="Instance.Setup.Initialize.Success">Instance settings initialized!</sys:String>
<sys:String x:Key="Instance.Setup.Memory.Used">Memory used</sys:String>
<sys:String x:Key="Instance.Setup.OpenGlobalSettings">Global settings</sys:String>
<!-- Instance.Setup.Advanced -->
<sys:String x:Key="Instance.Setup.Advanced.Classpath">Additional classpath prefix</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.Classpath.ToolTip">Content will be added as Classpath entries at the head of this instance's launch arguments.&#xA;Absolute paths are required. Separate multiple entries with English semicolons (;).</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.DisableFileVerification">Disable file verification</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.DisableFileVerification.ToolTip">Do not modify Assets at all; do not verify Libraries, third-party login libraries, or the instance main Jar file for modifications.&#xA;Do not enable this unless you have modified related files.</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.GameArgs.ToolTip">Content in this text box will be appended directly to the end of the launch arguments.&#xA;For example, entering --demo will launch the game in demo mode.&#xA;Supports substitution tokens from the Minecraft instance JSON, e.g. ${library_directory}.&#xA;&#xA;Leave empty to follow the global setting.</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.IgnoreJavaCompatibility">Ignore Java compatibility warning</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.IgnoreJavaCompatibility.ToolTip">If a manually selected Java is incompatible with this instance, automatically skip the compatibility warning popup and force the use of the manually selected Java.</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.JvmArgs.ToolTip">Additional JVM arguments used when launching Minecraft. Do not modify unless you are certain what you are doing.&#xA;Supports substitution tokens from the Minecraft instance JSON, e.g. ${library_directory}.&#xA;&#xA;Leave empty to follow the global setting.</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.PreLaunchCommand.ToolTip">This does not override the global setting: the global command runs first, then the instance command.&#xA;&#xA;Executes a command or program before launching Minecraft, using Windows command prompt syntax.&#xA;&#xA;The following substitution tokens can be used for relative paths (paths end with \):&#xA; · {path}: PCL CE executable folder&#xA; · {minecraft}: .minecraft folder&#xA; · {verpath}: Instance folder (.minecraft\versions\instance-name\)&#xA; · {verindie}: Version folder when isolation is on, .minecraft folder otherwise&#xA; · {java}: Java runtime folder&#xA;&#xA;Additionally, these tokens are also supported:&#xA; · {user}: Player name&#xA; · {login}: Player login method&#xA; · {uuid}: Player UUID&#xA; · {name}: Game instance name&#xA; · {date}, {time}: Current system time&#xA; · {version}: Game vanilla version&#xA;&#xA;Examples:&#xA; · "{verpath}test.exe" : Run test.exe in the instance folder&#xA; · "{java}java.exe" -jar "{verpath}test.jar" : Run test.jar from the instance folder with Java&#xA; · notepad "{verindie}options.txt" : Open the instance settings file with Notepad&#xA;&#xA;Always wrap paths in double quotes to avoid failures caused by spaces.&#xA;The working directory when executing the command is the current .minecraft folder.</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.UseDebugLog4j">Use debug-level log4j configuration</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.UseDebugLog4j.Message">This option changes the game log level to the minimum, which may consume substantial disk space and potentially affect game performance. It may also pose some security risks. Do not change this option if you don't know what you're doing!&#xA;Are you sure you want to continue?</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.UseGlobalProxy">Follow launcher network proxy settings</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.UseGlobalProxy.ToolTip">Use the proxy configured in [Settings - Misc - Network - HTTP Proxy] to launch the game.&#xA;Incorrect global settings will prevent login!</sys:String>
<!-- Instance.Setup.Options -->
<sys:String x:Key="Instance.Setup.Options.CustomInfo.ToolTip">This info is displayed at the bottom-left of the game's main screen and the top-left of the F3 debug screen.&#xA;Leave empty to follow the global setting.</sys:String>
<sys:String x:Key="Instance.Setup.Options.InstanceIsolation">Instance isolation</sys:String>
<sys:String x:Key="Instance.Setup.Options.InstanceIsolation.Disabled.ToolTip">Share saves, mods, and other files with instances that have isolation disabled.&#xA;If multiple instances with mods are present, mod conflicts may cause crashes.</sys:String>
<sys:String x:Key="Instance.Setup.Options.InstanceIsolation.Enabled">Enabled</sys:String>
<sys:String x:Key="Instance.Setup.Options.InstanceIsolation.Enabled.ToolTip">Saves, mods, and other files are kept separate from other instances, without interfering with each other.&#xA;This prevents cross-instance save sharing, but can help avoid mod conflicts.</sys:String>
<sys:String x:Key="Instance.Setup.Options.InstanceIsolation.Message">After changing instance isolation, you may need to manually migrate save files, mods, and other files to the new game folder.&#xA;If you find your saves are missing after the change, reverting this setting will restore them.&#xA;If you don't know how to migrate saves, it is not recommended to change this setting!</sys:String>
<sys:String x:Key="Instance.Setup.Options.WindowTitle.Default">Default window title</sys:String>
<sys:String x:Key="Instance.Setup.Options.WindowTitle.Default.ToolTip">Whether the game window title should keep the default.</sys:String>
<sys:String x:Key="Instance.Setup.Options.WindowTitle.ToolTip">Customize the game window title. Leave empty to follow the global setting.&#xA;The following replacement tokens are supported:&#xA; · {user}: Player name&#xA; · {login}: Player login method&#xA; · {name}: Game instance name&#xA; · {date}, {time}: Current system time&#xA; · {version}: Game vanilla version&#xA;Note: If your instance uses the Modern UI mod, this setting will be ineffective.</sys:String>
<!-- Instance.Setup.Options.Java -->
<sys:String x:Key="Instance.Setup.Options.Java">Game Java</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.AutoSelect">Auto-select</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.Details.ToolTip">Path: {0}&#xA;Version: {1}&#xA;Source: {2}</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.InvalidPathToolTip">Invalid path: {0}&#xA;Click this item to re-select a valid Java</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.LoadFailed">Failed to load list, please retry</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.Loading">Loading…</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.NoRuntime">No available Java runtime detected</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.NoRuntime.ToolTip">Please manually specify a Java path in settings, or click "Scan" to re-detect</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.PathOutOfRange">Path is out of allowed range. Please select a file within or under the launcher directory.</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.RelativePathToolTip">Relative path: {0}&#xA;Resolved path: {1}</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.SelectRelative">Select Java in launcher directory</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.SelectRelative.Invalid">Select Java in launcher directory (current path is invalid)</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.SelectRelative.WithJava">Java in launcher directory | {0}</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.SelectRelativeToolTip">Select a Java path relative to the instance directory</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.ToolTip">If Java is placed in the game or PCL CE folder, PCL CE will prefer selecting it during auto-detection.</sys:String>
<!-- Instance.Setup.Server -->
<sys:String x:Key="Instance.Setup.Server.AuthServer">Target auth server</sys:String>
<sys:String x:Key="Instance.Setup.Server.AuthServer.AutoFormatted">Auth server address automatically formatted!</sys:String>
<sys:String x:Key="Instance.Setup.Server.AuthServer.Hint">Full address of the Yggdrasil API authentication server</sys:String>
<sys:String x:Key="Instance.Setup.Server.AutoJoin">Automatic join</sys:String>
<sys:String x:Key="Instance.Setup.Server.AutoJoin.ToolTip">Automatically join a server after Minecraft opens.&#xA;Separate IP and port with an colon, e.g. 233.233.233.233:12345.</sys:String>
<sys:String x:Key="Instance.Setup.Server.CreateProfile">Create profile</sys:String>
<sys:String x:Key="Instance.Setup.Server.HttpWarning">Using HTTP for login operations may expose your credentials in plaintext.&#xA;Unless you have a specific reason, always use HTTPS!</sys:String>
<sys:String x:Key="Instance.Setup.Server.LittleSkin.Name">LittleSkin login</sys:String>
<sys:String x:Key="Instance.Setup.Server.LittleSkin.Override.Continue">Continue</sys:String>
<sys:String x:Key="Instance.Setup.Server.LittleSkin.Override.Message">Your third-party login settings will be overridden with LittleSkin login.&#xA;Do not proceed unless you are the server owner, or the server owner has instructed you to do so.&#xA;&#xA;Are you sure you want to override the current settings?</sys:String>
<sys:String x:Key="Instance.Setup.Server.LittleSkin.Override.Title">Confirm setting override</sys:String>
<sys:String x:Key="Instance.Setup.Server.LockLoginMethod">Lock login method</sys:String>
<sys:String x:Key="Instance.Setup.Server.LockLoginMethod.Message">You are about to lock the login method for this instance. Once locked, the login method requirement cannot be changed, and the game must be launched using the specified login method.&#xA;This feature may help some server owners.&#xA;Continue?</sys:String>
<sys:String x:Key="Instance.Setup.Server.LockLoginMethod.Title">Confirm login method lock</sys:String>
<sys:String x:Key="Instance.Setup.Server.LoginLockHint">The authentication method for this instance has been locked and cannot be changed. Please use the required authentication method to launch the game.</sys:String>
<sys:String x:Key="Instance.Setup.Server.LoginMethod.AuthOnly">Third-party login only (Authlib-Injector)</sys:String>
<sys:String x:Key="Instance.Setup.Server.LoginMethod.MicrosoftOnly">Microsoft login only</sys:String>
<sys:String x:Key="Instance.Setup.Server.LoginMethod.MicrosoftOrAuth">Microsoft or third-party login (Authlib-Injector)</sys:String>
<sys:String x:Key="Instance.Setup.Server.LoginMethod.Unlimited">No restriction</sys:String>
<sys:String x:Key="Instance.Setup.Server.Name">Server name</sys:String>
<sys:String x:Key="Instance.Setup.Server.Name.Hint">Server name displayed on the login page</sys:String>
<sys:String x:Key="Instance.Setup.Server.Register">Registration link</sys:String>
<sys:String x:Key="Instance.Setup.Server.Register.Hint">Web address for account registration, e.g. https://xxxxx.xxx/auth/register</sys:String>
<sys:String x:Key="Instance.Setup.Server.RestrictLogin">Restrict login method</sys:String>
<sys:String x:Key="Instance.Setup.Server.SetLittleSkin">Set to LittleSkin</sys:String>
<sys:String x:Key="Instance.Setup.Server.SslWarning">Disabling SSL certificate verification may expose your login credentials to third-party interception.&#xA;Unless you have a specific reason, always keep SSL certificate verification enabled!</sys:String>
<sys:String x:Key="Instance.Setup.Server.Title">Server</sys:String>
<!-- Launch -->
<sys:String x:Key="Launch.Error.ExportScriptTitle">Failed to export launch script</sys:String>
<sys:String x:Key="Launch.Error.OperationFailed">A launch page operation failed. See the details below.</sys:String>
<sys:String x:Key="Launch.Error.Title">Launch failed</sys:String>
<sys:String x:Key="Launch.Right.CommunityHint.HidePrompt">To permanently hide this notice, enter the correct PCL CE development organization name.</sys:String>
<sys:String x:Key="Launch.Right.CommunityHint.InputTitle">Enter the PCL CE development organization name</sys:String>
<sys:String x:Key="Launch.Right.CommunityHint.Message">You are using PCL CE (PCL Community Edition)! This version is independently developed and maintained, and differs from the original version in its development path and user experience.&#xA;&#xA;If you downloaded the PCL CE by accident, we strongly recommend downloading the original PCL version for long-term use, as this distribution may not be user-friendly for newcomers.&#xA;Additionally, please submit PCL CE issues to the PCL CE repository, and do not report PCL CE issues to the original repository!</sys:String>
<sys:String x:Key="Launch.Right.CommunityHint.Title">PCL CE notice</sys:String>
<sys:String x:Key="Launch.Right.CommunityHint.WrongInput">Not quite right...</sys:String>
<sys:String x:Key="Launch.Right.Log.Title">Launch log</sys:String>
<!-- Launch.Account -->
<sys:String x:Key="Launch.Account.Back">« Back</sys:String>
<sys:String x:Key="Launch.Account.Error.SwitchPage">Failed to switch login page. ({0})</sys:String>
<sys:String x:Key="Launch.Account.Login">Log in</sys:String>
<sys:String x:Key="Launch.Account.LoginCancelled">Login canceled!</sys:String>
<sys:String x:Key="Launch.Account.Type.Microsoft">Microsoft</sys:String>
<sys:String x:Key="Launch.Account.Type.Offline">Offline</sys:String>
<sys:String x:Key="Launch.Account.Type.ThirdParty">Third-party</sys:String>
<!-- Launch.Account.Auth -->
<sys:String x:Key="Launch.Account.Auth.Back">Back</sys:String>
<sys:String x:Key="Launch.Account.Auth.Cancelled">Login canceled!</sys:String>
<sys:String x:Key="Launch.Account.Auth.ChangeProfile">Switch profile</sys:String>
<sys:String x:Key="Launch.Account.Auth.Email">Email</sys:String>
<sys:String x:Key="Launch.Account.Auth.EmptyFields">Authentication server, username, and password cannot be empty!</sys:String>
<sys:String x:Key="Launch.Account.Auth.ForgotPassword">Forgot password</sys:String>
<sys:String x:Key="Launch.Account.Auth.InvalidServer">The authentication server address is invalid.</sys:String>
<sys:String x:Key="Launch.Account.Auth.Login">Log in</sys:String>
<sys:String x:Key="Launch.Account.Auth.LoginFailed">Third-party login failed.</sys:String>
<sys:String x:Key="Launch.Account.Auth.LoginFailed.WithDetail">Third-party login failed.&#xA;&#xA;Original error:&#xA;{0}</sys:String>
<sys:String x:Key="Launch.Account.Auth.Logout">Log out</sys:String>
<sys:String x:Key="Launch.Account.Auth.Password">Password</sys:String>
<sys:String x:Key="Launch.Account.Auth.Preset.LittleSkin">Preset - LittleSkin</sys:String>
<sys:String x:Key="Launch.Account.Auth.Register">Register</sys:String>
<sys:String x:Key="Launch.Account.Auth.Server">Authentication server</sys:String>
<sys:String x:Key="Launch.Account.Auth.ServerLabel">Authentication server: {0}</sys:String>
<!-- Launch.Account.LoginDialog -->
<sys:String x:Key="Launch.Account.LoginDialog.CopyCode">Copy code</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.Error.Init">Failed to initialize Microsoft login dialog</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.Error.Load">Failed to load Microsoft login dialog</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.MicrosoftInstructions">The login page will open automatically. Enter the authorization code {0} (it will be copied automatically).&#xA;&#xA;If you have network issues and the page fails to load, please try again.&#xA;You can also open {1} on another device and enter the code manually.</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.MicrosoftInstructions.WithAutoFill">The login page will open automatically, and the authorization code will be filled in automatically.&#xA;&#xA;If you have network issues and the page fails to load, please try again.&#xA;If the code is not filled in automatically, paste this authorization code {0} manually (it will be copied automatically).&#xA;You can also open {1} on another device and enter the code manually.</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.MinecraftLogin">Log in to Minecraft</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.PollingFailed">Microsoft login polling failed</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.ReopenWebpage">Reopen webpage</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.Success">Successfully logged in from the webpage!</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.Title">Test title text</sys:String>
<!-- Launch.Account.Microsoft -->
<sys:String x:Key="Launch.Account.Microsoft.Error.OperationFailed">A Microsoft sign-in operation failed. See the details below.</sys:String>
<sys:String x:Key="Launch.Account.Microsoft.Error.Unknown">Unknown error!</sys:String>
<sys:String x:Key="Launch.Account.Microsoft.LoginFailed.Message">Microsoft login authentication failed.&#xA;Please consider disabling the "Verify SSL certificates for Microsoft login" option in "Settings → Miscellaneous" and try again.&#xA;&#xA;Original error message:</sys:String>
<sys:String x:Key="Launch.Account.Microsoft.LoginFailed.Title">Microsoft login authentication failed</sys:String>
<sys:String x:Key="Launch.Account.Microsoft.LoginFailed.WithDetail">Microsoft login failed.&#xA;&#xA;Original error:&#xA;{0}</sys:String>
<sys:String x:Key="Launch.Account.Microsoft.Purchase">» Purchase</sys:String>
<sys:String x:Key="Launch.Account.Microsoft.Start">Log in</sys:String>
<sys:String x:Key="Launch.Account.Microsoft.Website">» Website</sys:String>
<!-- Launch.Account.Offline -->
<sys:String x:Key="Launch.Account.Offline.Back">Back</sys:String>
<sys:String x:Key="Launch.Account.Offline.Create">Create</sys:String>
<sys:String x:Key="Launch.Account.Offline.InvalidPlayerId.Message">Your username does not meet the standard (3 - 16 characters, letters, numbers, and underscores only), which may cause some game versions to fail to launch or encounter errors.&#xA;It is strongly recommended to use a standard username!&#xA;If you insist on using this username, you can continue.</sys:String>
<sys:String x:Key="Launch.Account.Offline.InvalidPlayerId.Title">Invalid username</sys:String>
<sys:String x:Key="Launch.Account.Offline.InvalidUuid">UUID does not meet requirements: {0}</sys:String>
<sys:String x:Key="Launch.Account.Offline.PlayerId">Username</sys:String>
<sys:String x:Key="Launch.Account.Offline.PlayerId.ToolTip">3 - 16 characters, letters, numbers, and underscores only</sys:String>
<sys:String x:Key="Launch.Account.Offline.Uuid.Custom.Input.ToolTip">Must be a 32-character hexadecimal string without hyphens</sys:String>
<sys:String x:Key="Launch.Account.Offline.Uuid.Custom.ToolTip">For advanced users. If you do not know what this is, do not select this option.</sys:String>
<sys:String x:Key="Launch.Account.Offline.Uuid.Legacy">Legacy</sys:String>
<sys:String x:Key="Launch.Account.Offline.Uuid.Legacy.ToolTip">Use legacy PCL CE UUID generation rules.&#xA;If some single-player save data is missing after upgrading from an older PCL CE version, try this option.</sys:String>
<sys:String x:Key="Launch.Account.Offline.Uuid.Standard">Standard</sys:String>
<sys:String x:Key="Launch.Account.Offline.Uuid.Standard.ToolTip">[Recommended] The generated UUID is consistent with other launchers such as HMCL, BakaXL, and Bukkit.</sys:String>
<sys:String x:Key="Launch.Account.Offline.UuidStandard">UUID standard</sys:String>
<!-- Launch.Account.Profile -->
<sys:String x:Key="Launch.Account.Profile.ChangeAuthServerName">Change authentication server name</sys:String>
<sys:String x:Key="Launch.Account.Profile.ChangeUuid">Change UUID</sys:String>
<sys:String x:Key="Launch.Account.Profile.CopyUuid">Copy UUID</sys:String>
<sys:String x:Key="Launch.Account.Profile.Create">New profile</sys:String>
<sys:String x:Key="Launch.Account.Profile.Create.SelectAuthType.Title">New profile - Select login type</sys:String>
<sys:String x:Key="Launch.Account.Profile.CreateAndSelectHint">Create and select a profile before launching the game</sys:String>
<sys:String x:Key="Launch.Account.Profile.RequireMicrosoftHint">A Microsoft profile is required to create profiles using other login types</sys:String>
<sys:String x:Key="Launch.Account.Profile.Created">Profile created!</sys:String>
<sys:String x:Key="Launch.Account.Profile.Delete">Delete profile</sys:String>
<sys:String x:Key="Launch.Account.Profile.DeleteConfirm.Message">You are about to delete this profile. This action cannot be undone.&#xA;Are you sure you want to continue?</sys:String>
<sys:String x:Key="Launch.Account.Profile.DeleteConfirm.Title">Confirm profile deletion</sys:String>
<sys:String x:Key="Launch.Account.Profile.Deleted">Profile deleted!</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditServerName.Message">Enter the new authentication server name.</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditServerName.Title">Change authentication server name</sys:String>
<sys:String x:Key="Launch.Account.Profile.Error.ChangeId">Failed to change username.</sys:String>
<sys:String x:Key="Launch.Account.Profile.Error.ChangeSkin">Failed to change skin</sys:String>
<sys:String x:Key="Launch.Account.Profile.Error.Corrupted">Failed to read profile data. The file may be corrupted; the profile file has been backed up and reset.</sys:String>
<sys:String x:Key="Launch.Account.Profile.Error.Read">Failed to read profile list.</sys:String>
<sys:String x:Key="Launch.Account.Profile.Error.Write">Failed to write profile list.</sys:String>
<sys:String x:Key="Launch.Account.Profile.ImportExport">Import / Export</sys:String>
<sys:String x:Key="Launch.Account.Profile.Saved">Profile saved!</sys:String>
<sys:String x:Key="Launch.Account.Profile.SelectHint">Select a profile before launching the game</sys:String>
<sys:String x:Key="Launch.Account.Profile.Uuid.ChangeTitle">Change UUID for profile {0}</sys:String>
<sys:String x:Key="Launch.Account.Profile.Uuid.Hint">32 characters, no hyphens</sys:String>
<sys:String x:Key="Launch.Account.Profile.Uuid.InvalidChars">UUID should only contain letters and numbers!</sys:String>
<sys:String x:Key="Launch.Account.Profile.Uuid.Legacy">Legacy PCL UUID (if single-player save data is missing, try this option)</sys:String>
<sys:String x:Key="Launch.Account.Profile.Uuid.SelectType.Title">New profile - Select UUID type</sys:String>
<sys:String x:Key="Launch.Account.Profile.Uuid.Standard">Standard UUID (recommended)</sys:String>
<sys:String x:Key="Launch.Account.Profile.Validation.EmptyUsername">Username cannot be empty!</sys:String>
<sys:String x:Key="Launch.Account.Profile.Validation.QuoteInUsername">Username cannot contain quotation marks!</sys:String>
<sys:String x:Key="Launch.Account.Profile.Validation.UnknownAuthType">Unknown login type.</sys:String>
<sys:String x:Key="Launch.Account.Profile.Validation.UsernameTooLong">Starting from version 1.20.3, usernames can contain a maximum of 16 characters!</sys:String>
<!-- Launch.Account.Profile.EditPlayerId -->
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Confirm.Message">Note: Username can only be changed once every 30 days. Please consider carefully!</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Confirm.Title">Confirm change</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Cooldown">After changing your username for the first time, you must wait 30 days before changing it again. You can check the exact date on the official website.</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Duplicate">This username is already in use. Please choose a different one.</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Empty">The username cannot be empty.</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Failed.Title">Failed to change username</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Hint">3 - 16 characters, letters, numbers, and underscores only</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.MicrosoftWarning">The username can only be changed once every 30 days. Please consider carefully!</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.NotAllowed">This username contains invalid characters other than letters, numbers and underscores.</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Success">Username changed successfully. Current username: {0}</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Title">Enter a new username</sys:String>
<!-- Launch.Account.ProfileSkin -->
<sys:String x:Key="Launch.Account.ProfileSkin.CapeUnsupported">This profile does not support changing the cape!</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.ChangeCape">Change cape</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.ChangeSkin">Change skin</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.EditPassword">Edit password</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.EditProfile">Edit profile</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.EditUsername">Edit username</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.PasswordUnsupported">This profile does not support changing the password!</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.RefreshSkin">Refresh</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.SaveSkin">Save skin</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.SkinUnsupported">This profile does not support changing the skin!</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.SkinsAndCapes">Skins and capes</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.SwitchProfile">Switch accounts</sys:String>
<!-- Launch.Home -->
<sys:String x:Key="Launch.Home.Button.Download">Download</sys:String>
<sys:String x:Key="Launch.Home.Button.Launch">Launch</sys:String>
<sys:String x:Key="Launch.Home.Button.Loading">Loading</sys:String>
<sys:String x:Key="Launch.Home.Instance.InstallingCannotLaunch">The current instance is being installed and cannot be launched!</sys:String>
<sys:String x:Key="Launch.Home.Instance.InstallingCannotSetup">The current instance is being installed and cannot be configured!</sys:String>
<sys:String x:Key="Launch.Home.Instance.Loading">Loading, please wait</sys:String>
<sys:String x:Key="Launch.Home.Instance.NotFound">No instance available</sys:String>
<sys:String x:Key="Launch.Home.InstanceSettings">Properties</sys:String>
<sys:String x:Key="Launch.Home.SelectInstance">Selection</sys:String>
<sys:String x:Key="Launch.Home.VersionList.Loading">Getting version list...</sys:String>
<!-- Launch.Homepage -->
<sys:String x:Key="Launch.Homepage.Error.Download">Failed to download homepage. ({0})</sys:String>
<sys:String x:Key="Launch.Homepage.Error.ExternalFile">Failed to read external file. ({0})</sys:String>
<sys:String x:Key="Launch.Homepage.LoadFailed.Message">The homepage content is malformed. Please check the following error information:&#xA;{0}</sys:String>
<sys:String x:Key="Launch.Homepage.LoadFailed.Retry">Retry</sys:String>
<sys:String x:Key="Launch.Homepage.LoadFailed.Title">Failed to load homepage</sys:String>
<sys:String x:Key="Launch.Homepage.Loading">Loading homepage...</sys:String>
<sys:String x:Key="Launch.Homepage.News.Loading">Loading...</sys:String>
<sys:String x:Key="Launch.Homepage.News.ViewDetail">View details</sys:String>
<sys:String x:Key="Launch.Homepage.Preset.EchoCave.Removed">The Echo Cave preset has been removed as it only had a shell. Please go to Settings and select another homepage preset.</sys:String>
<sys:String x:Key="Launch.Homepage.SlowWarning">The homepage is loading too slowly ({0} seconds). Please report this issue to the homepage author, or temporarily stop using this homepage.</sys:String>
<sys:String x:Key="Launch.Homepage.Task.Download">Download homepage</sys:String>
<!-- Launch.Skin -->
<sys:String x:Key="Launch.Skin.ChangeCape">Change cape</sys:String>
<sys:String x:Key="Launch.Skin.ChangeSuccess">Skin changed successfully!</sys:String>
<sys:String x:Key="Launch.Skin.Error.AuthGet">Failed to get Authlib-Injector skin. ({0})</sys:String>
<sys:String x:Key="Launch.Skin.Error.AuthlibRateLimited">Failed to get the Authlib-Injector skin. Too many requests were made. Please try again in 5 minutes.</sys:String>
<sys:String x:Key="Launch.Skin.Error.MsGet">Failed to get Microsoft skin. ({0})</sys:String>
<sys:String x:Key="Launch.Skin.Error.MsRateLimited">Skin fetch has been rate-limited. Please try again in 5 minutes. ({0})</sys:String>
<sys:String x:Key="Launch.Skin.Fetching">Obtaining the skin, please wait!</sys:String>
<sys:String x:Key="Launch.Skin.File.Error">Skin file error</sys:String>
<sys:String x:Key="Launch.Skin.FileDialog.Filter">Skin file (*.png;*.jpg;*.webp)|*.png;*.jpg;*.webp</sys:String>
<sys:String x:Key="Launch.Skin.FileDialog.Title">Select skin file</sys:String>
<sys:String x:Key="Launch.Skin.FileTooLarge">The skin file must be smaller than 24 KiB. The selected file is {0} KiB.</sys:String>
<sys:String x:Key="Launch.Skin.InvalidSize">The skin image should be 64x32 pixels or 64x64 pixels!</sys:String>
<sys:String x:Key="Launch.Skin.Load.Error.Avatar">Failed to load avatar: {0}</sys:String>
<sys:String x:Key="Launch.Skin.Load.Error.Corrupted">Skin file is corrupted: {0}</sys:String>
<sys:String x:Key="Launch.Skin.Model.Alex">Alex model</sys:String>
<sys:String x:Key="Launch.Skin.Model.SelectMessage">Is this skin the Steve model (wide arms) or the Alex model (slim arms)?</sys:String>
<sys:String x:Key="Launch.Skin.Model.SelectTitle">Skin model</sys:String>
<sys:String x:Key="Launch.Skin.Model.Steve">Steve model</sys:String>
<sys:String x:Key="Launch.Skin.Model.UnknownHint">Please confirm the skin model on the skin download page before using this skin!</sys:String>
<sys:String x:Key="Launch.Skin.Refresh.Busy">Still obtaining the skin, please try again later!</sys:String>
<sys:String x:Key="Launch.Skin.Refresh.Error">Failed to refresh skin cache.</sys:String>
<sys:String x:Key="Launch.Skin.RefreshSuccess">The avatar has been refreshed!</sys:String>
<sys:String x:Key="Launch.Skin.Refreshing">Updating avatar...</sys:String>
<sys:String x:Key="Launch.Skin.Save.Error">Failed to save the skin file.</sys:String>
<sys:String x:Key="Launch.Skin.SaveAs">Save as</sys:String>
<sys:String x:Key="Launch.Skin.SaveDialog.Filter">Skin image file (*.png)|*.png</sys:String>
<sys:String x:Key="Launch.Skin.SaveDialog.Title">Select a location to save the skin file.</sys:String>
<sys:String x:Key="Launch.Skin.SaveSuccess">Skin saved successfully!</sys:String>
<!-- Launch.Skin.Cape -->
<sys:String x:Key="Launch.Skin.Cape.ChangeFailed">Failed to change the cape.</sys:String>
<sys:String x:Key="Launch.Skin.Cape.ChangeFailedWithReason">Failed to change the cape: {0}</sys:String>
<sys:String x:Key="Launch.Skin.Cape.ChangeSuccess">Cape changed successfully!</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Changing">Changing cape, please wait!</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Error.List">Failed to get player cape list.</sys:String>
<sys:String x:Key="Launch.Skin.Cape.FetchingList">Getting the list of capes, please wait...</sys:String>
<sys:String x:Key="Launch.Skin.Cape.LoginFailed">Failed to log in, and the cape cannot be changed!</sys:String>
<sys:String x:Key="Launch.Skin.Cape.None">None</sys:String>
<sys:String x:Key="Launch.Skin.Cape.SelectTitle">Choose a cape</sys:String>
<!-- Launch.Skin.Cape.Name -->
<sys:String x:Key="Launch.Skin.Cape.Name.Builder">Builder</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.CherryBlossom">Cherry Blossom</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Cobalt">Cobalt</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Common">Common</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Copper">Copper</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Crafter">Crafter</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.FifteenthAnniversary">15th Anniversary</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Followers">Follower's</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Founders">Founders</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Home">Home</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.MapMaker">Realms MapMaker</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Mcc">MCC 15th Year</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Menace">Menace</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Migrator">Migrator</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Minecon2011">MINECON 2011</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Minecon2012">MINECON 2012</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Minecon2013">MINECON 2013</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Minecon2015">MINECON 2015</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Minecon2016">MINECON 2016</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.MinecraftExperience">Minecraft Experience</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Moderator">Mojira Moderator</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.MojangOffice">Mojang Office</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.MoonlightTrail">Moonlight Trail</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Pan">Pan</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.PurpleHeart">Purple Heart</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Translator">Translator</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.TranslatorChinese">Chinese Translator</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Vanilla">Vanilla</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Yearn">Yearn</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.ZombieHorse">Zombie Horse</sys:String>
<!-- Launch.Skin.Change -->
<sys:String x:Key="Launch.Skin.Change.Busy">Changing skin, please wait!</sys:String>
<sys:String x:Key="Launch.Skin.Change.Error.MsRefresh">Failed to refresh the Microsoft skin after changing it.</sys:String>
<sys:String x:Key="Launch.Skin.Change.FailedWithDetail">Failed to change skin.&#xA;&#xA;Details:&#xA;{0}</sys:String>
<sys:String x:Key="Launch.Skin.Change.LoginFailed">Failed to log in, so the skin cannot be changed.</sys:String>
<sys:String x:Key="Launch.Skin.Change.Reauthenticating">Signing in. The skin change will continue after sign-in completes...</sys:String>
<sys:String x:Key="Launch.Skin.Change.Starting">Changing skin...</sys:String>
<sys:String x:Key="Launch.Skin.Change.Timeout.WithDetail">Failed to change skin because the connection to Mojang's skin server timed out. Please check your network connection.&#xA;&#xA;Details:&#xA;{0}</sys:String>
<sys:String x:Key="Launch.Skin.Change.ToolTip">Click to change (right-click for more options)</sys:String>
<!-- Launch.Status -->
<sys:String x:Key="Launch.Status.Completed">Completed</sys:String>
<sys:String x:Key="Launch.Status.CurrentStep">Current step</sys:String>
<sys:String x:Key="Launch.Status.DownloadLibs">Download libraries</sys:String>
<sys:String x:Key="Launch.Status.DownloadSpeed">Speed</sys:String>
<sys:String x:Key="Launch.Status.LaunchProgress">Progress</sys:String>
<sys:String x:Key="Launch.Status.LoginMethod">Login method</sys:String>
<sys:String x:Key="Launch.Status.MicrosoftLogin">Microsoft login</sys:String>
<sys:String x:Key="Launch.Status.Title.ExportingScript">Exporting launch script</sys:String>
<sys:String x:Key="Launch.Status.Title.Launched">Game launched</sys:String>
<sys:String x:Key="Launch.Status.Title.Launching">Launching...</sys:String>
<sys:String x:Key="Launch.Status.Trivia">Trivia</sys:String>
<sys:String x:Key="Launch.Status.Trivia.Placeholder">This is a test trivia text that should be long enough to span two lines.</sys:String>
<!-- Link -->
<sys:String x:Key="Link.Connection.Local">Local</sys:String>
<sys:String x:Key="Link.Connection.P2P">P2P</sys:String>
<sys:String x:Key="Link.Connection.Relay">Relay</sys:String>
<sys:String x:Key="Link.Connection.Unknown">Unknown</sys:String>
<sys:String x:Key="Link.Natayark.TokenFetchEmpty">Failed to get AccessToken and RefreshToken: response is empty</sys:String>
<sys:String x:Key="Link.Natayark.TokenParseFailed">Failed to get AccessToken and RefreshToken: unable to parse response</sys:String>
<sys:String x:Key="Link.Natayark.UserInfoFetchEmpty">Failed to get Natayark user information: response is empty</sys:String>
<sys:String x:Key="Link.Natayark.UserInfoParseFailed">Failed to get Natayark user information: unable to parse response</sys:String>
<sys:String x:Key="Link.Quality.Good">Good</sys:String>
<sys:String x:Key="Link.Quality.GoodDescription">The current network environment should not affect connection experience.&#xA;This environment is suitable as the lobby host.</sys:String>
<sys:String x:Key="Link.Quality.Normal">Normal</sys:String>
<sys:String x:Key="Link.Quality.NormalDescription">The current network environment may affect your connection experience.</sys:String>
<sys:String x:Key="Link.Quality.Poor">Poor</sys:String>
<sys:String x:Key="Link.Quality.PoorDescription">Some router and firewall settings may affect your connection experience.</sys:String>
<!-- Link.Lobby -->
<sys:String x:Key="Link.Lobby.CreateLobbyFailed">Failed to create lobby. Please check logs or report to the developer.</sys:String>
<sys:String x:Key="Link.Lobby.CreateRoomFailed">An error occurred while creating the room. Please check the logs and report this to the developer!</sys:String>
<sys:String x:Key="Link.Lobby.InitFailed">Lobby service initialization failed. Please check your network.</sys:String>
<sys:String x:Key="Link.Lobby.InvalidCodeFormat">Invalid lobby code format. Please check and try again!</sys:String>
<sys:String x:Key="Link.Lobby.JoinFailed">Failed to join lobby. It may not exist or may have been disbanded.</sys:String>
<sys:String x:Key="Link.Lobby.LoginRequired">Please log in to Natayark ID first!</sys:String>
<sys:String x:Key="Link.Lobby.MotdDesc"> - {0}</sys:String>
<sys:String x:Key="Link.Lobby.MotdFormat">PCL CE Lobby{0}</sys:String>
<sys:String x:Key="Link.Lobby.UnsupportedType">Unsupported lobby type: {0}</sys:String>
<sys:String x:Key="Link.Lobby.WorldNameFormat">{0} / {1} ({2})</sys:String>
<!-- Link.Mod -->
<sys:String x:Key="Link.Mod.AccountBanned">Your Natayark Network account status is abnormal and may be banned!</sys:String>
<sys:String x:Key="Link.Mod.DownloadComplete">Multiplayer components downloaded!</sys:String>
<sys:String x:Key="Link.Mod.DownloadEasyTierFailed">Failed to download EasyTier dependencies. Please check your network.</sys:String>
<sys:String x:Key="Link.Mod.DownloadingDeps">Downloading multiplayer dependencies. Please wait...</sys:String>
<sys:String x:Key="Link.Mod.DownloadingEasyTier">Downloading EasyTier. Please wait...</sys:String>
<sys:String x:Key="Link.Mod.EasyTierNotReady">EasyTier has not finished downloading. Please wait and try again!</sys:String>
<sys:String x:Key="Link.Mod.InvalidPlayerId">Minecraft player ID cannot contain separators (|)!</sys:String>
<sys:String x:Key="Link.Mod.LobbyUnavailable">Lobby is currently unavailable. Please try again later.</sys:String>
<sys:String x:Key="Link.Mod.LoginFirst">Please go to multiplayer settings and log in to Natayark Network first!</sys:String>
<sys:String x:Key="Link.Mod.NaidFetchFailed">Failed to fetch Natayark ID information</sys:String>
<sys:String x:Key="Link.Mod.ReLoginRequired">Please log in to your Natayark Network account again and try again!</sys:String>
<sys:String x:Key="Link.Mod.RealNameRequired">Please complete real-name verification at the Natayark Account Center first!</sys:String>
<sys:String x:Key="Link.Mod.Task.CleanCache">Clean caches and redundant components</sys:String>
<sys:String x:Key="Link.Mod.Task.DownloadEasyTier">Download EasyTier</sys:String>
<sys:String x:Key="Link.Mod.Task.ExtractFiles">Extract files</sys:String>
<sys:String x:Key="Link.Mod.Task.FetchAnnouncement">Fetch lobby announcement</sys:String>
<sys:String x:Key="Link.Mod.Task.InitLobby">Initialize lobby</sys:String>
<sys:String x:Key="Link.Mod.Task.InitLobbyUi">Initialize lobby UI</sys:String>
<sys:String x:Key="Link.Mod.Task.RefreshUi">Refresh UI</sys:String>
<sys:String x:Key="Link.Mod.UsernameOrLogin">Please set a username in Settings, or log in to Natayark Network first!</sys:String>
<!-- Link.Nat -->
<!-- Link.Nat.Type -->
<sys:String x:Key="Link.Nat.Type.FullCone">Moderate (full cone)</sys:String>
<sys:String x:Key="Link.Nat.Type.Open">Open</sys:String>
<sys:String x:Key="Link.Nat.Type.PortRestricted">Moderate (port restricted)</sys:String>
<sys:String x:Key="Link.Nat.Type.Restricted">Moderate (restricted)</sys:String>
<sys:String x:Key="Link.Nat.Type.Symmetric">Strict (symmetric)</sys:String>
<sys:String x:Key="Link.Nat.Type.SymmetricEasy">Strict (loose symmetric)</sys:String>
<sys:String x:Key="Link.Nat.Type.SymmetricFirewall">Strict (symmetric firewall)</sys:String>
<sys:String x:Key="Link.Nat.Type.UdpBlocked">Strict (UDP blocked)</sys:String>
<sys:String x:Key="Link.Nat.Type.Unknown">Unknown</sys:String>
<!-- LogPage -->
<sys:String x:Key="LogPage.Action.Clear">Clear</sys:String>
<sys:String x:Key="LogPage.Action.Export">Export</sys:String>
<sys:String x:Key="LogPage.Action.ExportStack">Dump</sys:String>
<sys:String x:Key="LogPage.Action.GameClosed">Game {0} closed.</sys:String>
<sys:String x:Key="LogPage.Action.KillProcess">Kill</sys:String>
<sys:String x:Key="LogPage.Action.Scroll">Scroll</sys:String>
<sys:String x:Key="LogPage.Empty.Description">Go to Instance settings → Overview → Test game to display live game logs.</sys:String>
<sys:String x:Key="LogPage.Empty.Title">No live logs</sys:String>
<sys:String x:Key="LogPage.Error.OperationFailed">A log page operation failed. See the details below.</sys:String>
<sys:String x:Key="LogPage.Export.GameLog.FileName">Game log - {0}.log</sys:String>
<sys:String x:Key="LogPage.Export.GameLog.Filter">Game log (*.log)|*.log</sys:String>
<sys:String x:Key="LogPage.Export.SelectLocation">Select export location</sys:String>
<sys:String x:Key="LogPage.Export.Success">Log exported!</sys:String>
<sys:String x:Key="LogPage.ExportStack.FileName">Game stack dump - {0}.log</sys:String>
<sys:String x:Key="LogPage.ExportStack.Filter">Game stack dump (*.log)|*.log</sys:String>
<sys:String x:Key="LogPage.ExportStack.Progress">Exporting stack dump. Please wait (may take 15 seconds to 1 minute).</sys:String>
<sys:String x:Key="LogPage.ExportStack.Success">Stack dump exported!</sys:String>
<sys:String x:Key="LogPage.Left.InstancesTitle">Test instances</sys:String>
<sys:String x:Key="LogPage.Level.Debug">Debug</sys:String>
<sys:String x:Key="LogPage.Level.Error">Error</sys:String>
<sys:String x:Key="LogPage.Level.Fatal">Fatal</sys:String>
<sys:String x:Key="LogPage.Level.Info">Info</sys:String>
<sys:String x:Key="LogPage.Level.Warn">Warn</sys:String>
<sys:String x:Key="LogPage.MaxLines.Hint">Logs are limited to {0} lines by default. Adjust this in the max log lines settings.</sys:String>
<sys:String x:Key="LogPage.MaxLines.Title">Max lines</sys:String>
<sys:String x:Key="LogPage.MaxLines.ToolTip">Maximum number of lines displayed. Older logs are automatically removed when this limit is exceeded.</sys:String>
<sys:String x:Key="LogPage.MaxLines.Unlimited">Unlimited</sys:String>
<sys:String x:Key="LogPage.Title">Live logs</sys:String>
<!-- Main -->
<sys:String x:Key="Main.April.Nope">Muda!</sys:String>
<sys:String x:Key="Main.DebugMode.Hint">[Debug mode] PCL CE is running in debug mode, which may cause performance degradation. Please do NOT enable it unless necessary!</sys:String>
<sys:String x:Key="Main.Error.OperationFailed">A launcher interface operation failed. See the details below.</sys:String>
<sys:String x:Key="Main.Error.ScrollToTopFailed">Could not return to the top of the page.</sys:String>
<sys:String x:Key="Main.Eula.Message">Before using PCL CE, please review and agree to the Terms of Service and Disclaimer.</sys:String>
<sys:String x:Key="Main.Eula.Title">ToS &amp; Disclaimer authorization</sys:String>
<sys:String x:Key="Main.Eula.View">ToS &amp; Disclaimer</sys:String>
<sys:String x:Key="Main.Exit.HasDownloadingTask">There is a download task in progress. Are you sure you want to quit?</sys:String>
<sys:String x:Key="Main.Extra.BackToTop.ToolTip">Back to top</sys:String>
<sys:String x:Key="Main.Extra.GameLog.ToolTip">Launch log</sys:String>
<sys:String x:Key="Main.Extra.GiveUp.ToolTip">Surrender</sys:String>
<sys:String x:Key="Main.Extra.ShutdownMinecraft.ToolTip">Close Minecraft</sys:String>
<sys:String x:Key="Main.Extra.TaskManager.ToolTip">Task manager</sys:String>
<sys:String x:Key="Main.Extra.UpdateRestart.ToolTip">Restart PCL CE to apply software updates</sys:String>
<sys:String x:Key="Main.Extra.UpdateRestart.ToolTipWithVersion">Restart PCL CE to apply software updates ({0} → {1})</sys:String>
<sys:String x:Key="Main.HiddenFeature.Disabled">The feature hiding setting has been temporarily disabled!</sys:String>
<sys:String x:Key="Main.HiddenFeature.Enabled">The feature hiding setting has been re-enabled!</sys:String>
<sys:String x:Key="Main.ShutdownMinecraft.Success">Minecraft instance closed!</sys:String>
<sys:String x:Key="Main.SpecialVersion.CiHint">The current PCL CE build is an automated CI build.&#xA;This build includes the latest bug fixes, optimizations, and new features, but its performance and stability are lower. It is not suitable for daily use or creating modpacks.&#xA;&#xA;Unless a community developer asks you to use it, or you specifically want to do so, please use the latest stable version instead.</sys:String>
<sys:String x:Key="Main.SpecialVersion.DebugHint">The current PCL CE build is a Debug build.&#xA;This build is only intended for developer debugging and may have serious performance issues or various unexpected network problems.&#xA;&#xA;Problems caused by non-developer users running this build are not supported by the community, and related issues may be closed directly.&#xA;Unless you are a developer, please delete this build immediately and use the latest stable version instead.</sys:String>
<sys:String x:Key="Main.SpecialVersion.HideHintNotice">You can add the PCL_DISABLE_DEBUG_HINT environment variable with any value to hide this prompt.</sys:String>
<sys:String x:Key="Main.SpecialVersion.IUnderstand">I know what I am doing</sys:String>
<sys:String x:Key="Main.SpecialVersion.OpenDownloadPageAndExit">Open latest download page and exit</sys:String>
<sys:String x:Key="Main.SpecialVersion.Title">Special build notice</sys:String>
<sys:String x:Key="Main.Tab.Download">Download</sys:String>
<sys:String x:Key="Main.Tab.Launch">Launch</sys:String>
<sys:String x:Key="Main.Tab.Settings">Settings</sys:String>
<sys:String x:Key="Main.Tab.Tools">Tools</sys:String>
<sys:String x:Key="Main.Telemetry.Message">After telemetry data collection is enabled, the launcher will collect and report errors and device environment information. This can help developers fix potential issues and better plan and develop the launcher.&#xA;If this feature is enabled, we will collect the following information:&#xA;&#xA;- Errors that occur in the launcher&#xA;- Launcher version information and identifier&#xA;- Windows system version and architecture&#xA;- Installed physical memory size&#xA;- NAT and IPv6 support status&#xA;- Whether you have used the original PCL, HMCL, or BakaXL&#xA;&#xA;This data is not associated with you, and we will never sell it to any third party.&#xA;If you do not want to enable telemetry, you can choose to decline. This will not affect normal use of other features, but may affect developers' ability to fix potential bugs.&#xA;You can change this setting at any time in launcher settings.</sys:String>
<sys:String x:Key="Main.Telemetry.Title">Enable telemetry data collection</sys:String>
<sys:String x:Key="Main.Title.GameLog">Launch log</sys:String>
<sys:String x:Key="Main.Title.InstanceSelect">Selection</sys:String>
<sys:String x:Key="Main.Title.InstanceSetup">Properties - {0}</sys:String>
<sys:String x:Key="Main.Title.ResourceDownload">Resource download - {0}</sys:String>
<sys:String x:Key="Main.Title.SaveManagement">Save management - {0}</sys:String>
<sys:String x:Key="Main.Title.TaskManager">Task manager</sys:String>
<sys:String x:Key="Main.UpdateLog.Empty">Welcome!</sys:String>
<sys:String x:Key="Main.UpdateLog.FullChangelog">Full changelog</sys:String>
<sys:String x:Key="Main.UpdateLog.Title">PCL CE has been updated to {0} {1}</sys:String>
<sys:String x:Key="Main.HomepageReset.Title">Homepage preset setting has been reset</sys:String>
<sys:String x:Key="Main.HomepageReset.Content">The homepage preset setting has been reset due to the launcher update.&#xA;Please go to Settings - Personalization and select the preset again.</sys:String>
<!-- Main.FileDrag -->
<sys:String x:Key="Main.FileDrag.AuthlibInvalid">The Authlib authentication server does not match the URL format ({0})!</sys:String>
<sys:String x:Key="Main.FileDrag.CreateAuthlibProfile">Do you want to create a new third-party authentication profile?&#xA;Authentication server address: {0}</sys:String>
<sys:String x:Key="Main.FileDrag.CreateAuthlibProfileTitle">Create new third-party authentication profile</sys:String>
<sys:String x:Key="Main.FileDrag.ExtractFirst">Please extract the file before dragging it in!</sys:String>
<sys:String x:Key="Main.FileDrag.FileNotFound">The file dragged in does not exist: {0}</sys:String>
<sys:String x:Key="Main.FileDrag.FileOnly">Please drag in a file instead of a folder!</sys:String>
<sys:String x:Key="Main.FileDrag.HomepageExists">A homepage file already exists. Do you want to override it?</sys:String>
<sys:String x:Key="Main.FileDrag.HomepageLoaded">Custom homepage file loaded!</sys:String>
<sys:String x:Key="Main.FileDrag.Imported">Imported {0}</sys:String>
<sys:String x:Key="Main.FileDrag.OverwriteTitle">Override confirmation</sys:String>
<sys:String x:Key="Main.FileDrag.SameFileExists">A file with the same name already exists: {0}</sys:String>
<sys:String x:Key="Main.FileDrag.SameFolderExists">A folder with the same name was found and cannot be pasted: {0}</sys:String>
<sys:String x:Key="Main.FileDrag.SameTypeOnly">Please drag in only files of the same type at a time.</sys:String>
<sys:String x:Key="Main.FileDrag.SaveImportFailed">Failed to import save</sys:String>
<sys:String x:Key="Main.FileDrag.SaveInvalid">The imported save is missing level.dat!</sys:String>
<sys:String x:Key="Main.FileDrag.SaveNotFound">The archive does not contain an importable Minecraft save!</sys:String>
<sys:String x:Key="Main.FileDrag.UnknownOperation">PCL CE cannot determine which file drag operation to perform...</sys:String>
<!-- Minecraft -->
<sys:String x:Key="Minecraft.Comp.Error.OperationFailed">A component operation failed. See the details below.</sys:String>
<sys:String x:Key="Minecraft.Folder.Error.OperationFailed">A Minecraft folder operation failed. See the details below.</sys:String>
<sys:String x:Key="Minecraft.Skin.Error.InvalidSkinType">Invalid skin type: {0}</sys:String>
<sys:String x:Key="Minecraft.Skin.Error.NoCustomSkin">User has not set a custom skin</sys:String>
<sys:String x:Key="Minecraft.Skin.Error.NoSkinData">Skin response contains no skin data, possibly a user without custom skin</sys:String>
<sys:String x:Key="Minecraft.Skin.Error.OfflineNoSkin">Offline UUID has no official skin.</sys:String>
<sys:String x:Key="Minecraft.Skin.Error.PropertyNotFound">No matching property found in skin response</sys:String>
<sys:String x:Key="Minecraft.Skin.Error.SkinReturnEmpty">Skin return value is empty, possibly a user without custom skin</sys:String>
<sys:String x:Key="Minecraft.Skin.Error.UuidEmpty">UUID is empty.</sys:String>
<sys:String x:Key="Minecraft.Update.NewVersion">New version: {0}</sys:String>
<sys:String x:Key="Minecraft.Update.Title">Minecraft update</sys:String>
<sys:String x:Key="Minecraft.Update.UpdateTime">Update time: </sys:String>
<sys:String x:Key="Minecraft.Update.UpdatedAt">Updated: </sys:String>
<sys:String x:Key="Minecraft.Version.Unknown">Unknown version</sys:String>
<sys:String x:Key="Minecraft.Version.Vanilla">Vanilla</sys:String>
<!-- Minecraft.Download -->
<sys:String x:Key="Minecraft.Download.Loader.NewForge">New Forge</sys:String>
<sys:String x:Key="Minecraft.Download.ServerBatch.InstructionEula">If prompted "You need to agree to the EULA", edit eula.txt to agree!</sys:String>
<sys:String x:Key="Minecraft.Download.ServerBatch.InstructionJavaPath">If the server stops immediately, right-click edit this script and replace %JAVA_HOME% with !JAVA_HOME!</sys:String>
<sys:String x:Key="Minecraft.Download.ServerBatch.InstructionPclSettings">You can select this version as a suitable Java and enable it in PCL CE under [Settings → Launch options → Java path inside launcher]!</sys:String>
<sys:String x:Key="Minecraft.Download.ServerBatch.ServerStopped">Server stopped.</sys:String>
<sys:String x:Key="Minecraft.Download.ServerBatch.Title">{0} vanilla server</sys:String>
<!-- Minecraft.Download.Error -->
<sys:String x:Key="Minecraft.Download.Error.AddClientVersionFailed">Failed to add client version</sys:String>
<sys:String x:Key="Minecraft.Download.Error.AssetIndexAnalysisFailed">Failed to analyze asset index URL</sys:String>
<sys:String x:Key="Minecraft.Download.Error.BmclapiMissingTargetVersion">BMCLAPI missing target version {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Error.FabricMetaDownloadFailed">Fabric meta download failed: {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Error.ForgeOldInstallFailed">Failed to install Forge in old-style mode</sys:String>
<sys:String x:Key="Minecraft.Download.Error.ForgePromosResultTooShort">Result too short ({0})</sys:String>
<sys:String x:Key="Minecraft.Download.Error.ForgeRecommendedTooFew">Too few recommended versions ({0})</sys:String>
<sys:String x:Key="Minecraft.Download.Error.InstallationCanceled">Installation canceled.</sys:String>
<sys:String x:Key="Minecraft.Download.Error.InstallerFailedLastLine">The installer failed, and the last line is {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Error.InstanceAlreadyExists">Instance {0} already exists. Download it again?{1}This will overwrite the instance JSON and JAR files, but will not affect version-isolated files.</sys:String>
<sys:String x:Key="Minecraft.Download.Error.InstanceAlreadyExists.Title">Instance already exists</sys:String>
<sys:String x:Key="Minecraft.Download.Error.InstanceDownloading">This instance is already downloading!</sys:String>
<sys:String x:Key="Minecraft.Download.Error.JavaNotFoundInstallCanceled">Java was not found, installation canceled.</sys:String>
<sys:String x:Key="Minecraft.Download.Error.JavaVersionRequired">Java 8 or higher</sys:String>
<sys:String x:Key="Minecraft.Download.Error.JsonInvalid">{0} JSON is invalid. Path: {1}; beginning content: {2}</sys:String>
<sys:String x:Key="Minecraft.Download.Error.LiteLoaderInstallFailed">Failed to install new LiteLoader instance</sys:String>
<sys:String x:Key="Minecraft.Download.Error.LoaderDetailsNotFound">Unable to find details for {0} {1}-{2}!</sys:String>
<sys:String x:Key="Minecraft.Download.Error.LoaderInstallFailed">Failed to install new {0} instance</sys:String>
<sys:String x:Key="Minecraft.Download.Error.LoaderInstallerFailedLastLine">{0} installer failed. Log ending: {1}</sys:String>
<sys:String x:Key="Minecraft.Download.Error.LoaderInstallerRunFailed">Failed to run {0} installer</sys:String>
<sys:String x:Key="Minecraft.Download.Error.LoaderLibraryListFailed">Failed to get {0} library list</sys:String>
<sys:String x:Key="Minecraft.Download.Error.NoAvailableVersion">No available version</sys:String>
<sys:String x:Key="Minecraft.Download.Error.NoJarDownloadInfo">No JAR download info in base version {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Error.NoJsonDownloadAddress">No corresponding JSON download URL</sys:String>
<sys:String x:Key="Minecraft.Download.Error.NoOfficialServerDownload">Mojang has not provided an official server download for Minecraft {0}, so it cannot be downloaded.</sys:String>
<sys:String x:Key="Minecraft.Download.Error.NoVanillaLoaderSpecified">The MC installation folder cannot be specified unless a Vanilla downloader is specified</sys:String>
<sys:String x:Key="Minecraft.Download.Error.NotFound">No available version</sys:String>
<sys:String x:Key="Minecraft.Download.Error.OperationFailed">A Minecraft download operation failed. See the details below.</sys:String>
<sys:String x:Key="Minecraft.Download.Error.OptiFineForgeCompatMismatch">Version and Forge compatibility data mismatch</sys:String>
<sys:String x:Key="Minecraft.Download.Error.OptiFineInstallFailed.MethodA">Failed to install OptiFine (method A)</sys:String>
<sys:String x:Key="Minecraft.Download.Error.OptiFineInstallFailed.MethodB">Failed to install OptiFine (method B)</sys:String>
<sys:String x:Key="Minecraft.Download.Error.OptiFineInstallerRunFailed">Failed to run OptiFine installer</sys:String>
<sys:String x:Key="Minecraft.Download.Error.OptiFineTimeDataMismatch">Version and release time data mismatch</sys:String>
<sys:String x:Key="Minecraft.Download.Error.ServerDownloading">This server is already downloading!</sys:String>
<sys:String x:Key="Minecraft.Download.Error.Timeout">Download source connection timed out</sys:String>
<sys:String x:Key="Minecraft.Download.Error.UnknownMinecraftVersion">Unknown Minecraft version ({0})</sys:String>
<sys:String x:Key="Minecraft.Download.Error.VersionListOperationFailed">Failed to parse {0} version list ({1})</sys:String>
<!-- Minecraft.Download.Modpack -->
<sys:String x:Key="Minecraft.Download.Modpack.BundledLauncher.DoNotUse">Do not use</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.BundledLauncher.Message">The modpack appears to include its own launcher. Switch to using it to continue?&#xD;&#xA;Will open: {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.BundledLauncher.Title">Switch to modpack launcher?</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.BundledLauncher.UseBundled">Use bundled</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.EncryptedArchiveUnsupported">PCL CE cannot handle encrypted archives. Please extract and re-archive them in an unencrypted .zip format and try again.</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Error.OperationFailed">A modpack operation failed. See the details below.</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.FileDialog.Filter">Modpack files(*.rar;*.zip;*.mrpack)|*.rar;*.zip;*.mrpack</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.FileDialog.Title">Select modpack archive</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.InputInstanceName">Enter instance name</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Installing">This modpack is currently being installed!</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.InvalidGamePathChars">Game path cannot contain exclamation marks (!) or semicolons (;): {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.MissingGameVersion.Generic">This modpack does not provide game version information and cannot be installed!</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.MissingGameVersion.Hmcl">This HMCL modpack does not provide game version information and cannot be installed!</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.MissingGameVersion.McbbsAddons">This MCBBS modpack does not provide game version addon information and cannot be installed!</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.ModMissingRequiredInfoSkipped">A mod is missing required information and has been skipped: {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.OpenFailed">Failed to open modpack file</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.OptionalFile.Download">Download</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.OptionalFile.Message">Do you want to download the optional file {0} from the modpack?</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.OptionalFile.Skip">Skip</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.OptionalFile.Title">Download optional file</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.PathOutsideInstance.Message">A file path in the modpack exceeds the instance folder. Please report this issue to the modpack author!&#xA;Incorrect file: {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.PathOutsideInstance.Title">File path validation failed</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.QuiltUnsupported">Cannot install this modpack: Quilt is no longer supported by this launcher.</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.RarUnsupported">PCL CE cannot handle .rar archive files. Please extract and re-archive them in .zip format and try again!</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.SelectEmptyFolder.Message">Next, select an empty folder. The modpack will be installed there.</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.SelectTargetFolder.Title">Select installation target (must be an empty folder)</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.SomeModsDeleted">Some mod versions in the modpack have been deleted by the mod authors, so installation cannot continue. Please report this issue to the modpack author.</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.TargetFolderMustBeEmpty">Please select an empty folder as the installation target!</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Task.CurseForgeInstall">Install CurseForge modpack: {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Task.HmclInstall">Install HMCL modpack: {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Task.McbbsInstall">Install MCBBS modpack: {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Task.MmcInstall">Install MultiMC modpack: {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Task.ModrinthInstall">Install Modrinth modpack: {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.TooOldUnsupported">This modpack version is too old and no longer supported!</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.UnknownArchiveStructure">No suitable file structure was found. This may not be a Minecraft archive.</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.UnknownLoader">Cannot install modpack, unknown mod loader {0} (version {1})!</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.UnsupportedArchive">Failed to open the modpack file. The file may be corrupted or in an unsupported archive format.</sys:String>
<!-- Minecraft.Download.Modpack.Stage -->
<sys:String x:Key="Minecraft.Download.Modpack.Stage.BuildModsDownloadInfo">Construct mods download information</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.DownloadAdditions">Download additions</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.DownloadMods">Download mods</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.DownloadMods.MainLoader">Download mods (main loader)</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.ExtractArchive">Extract archive</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.ExtractModpack">Extract modpack files</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.FinalizeFiles">Final files organization</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.GameInstall">Game installation</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.ModpackInstall">Modpack installation</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.PrepareModsDownloadInfo">Prepare mods download information</sys:String>
<!-- Minecraft.Download.Stage -->
<sys:String x:Key="Minecraft.Download.Stage.AddIgnoreFlag">Add ignore flag</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeAssetsIndex">Analyze assets index URL</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeAssetsIndex.Background">Analyze assets index URL (background)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeAssetsIndex.Side">Analyze assets index URL (side loader)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeCoreJarUrl">Analyze core JAR URL</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeFabricLibraries">Analyze Fabric libraries</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeGameLibrariesSide">Analyze game libraries (side loader)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeLabyModLibraries">Analyze LabyMod libraries</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeLegacyFabricLibraries">Analyze Legacy Fabric libraries</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeLiteLoaderLibraries">Analyze LiteLoader libraries</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeLoaderLibraries">Analyze {0} libraries</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeMissingAssets">Analyze missing assets</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeMissingLibraries">Analyze missing libraries</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeOptiFineLibraries">Analyze OptiFine libraries</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeRequiredAssets.Side">Analyze required assets (side loader)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeVanillaAndLabyModLibrariesSide">Analyze Vanilla and LabyMod libraries (side loader)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeVanillaLibraries.Side">Analyze Vanilla libraries (side loader)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.BuildServer">Build server</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.CopyAssetsIndex.Background">Copy assets index (background)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DeleteIgnoreFlag">Delete ignore flag</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadAssets">Download assets</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadAssets.MainLoader">Download assets (main loader)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadAssets.Side">Download assets (side loader)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadAssetsIndex">Download assets index</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadAssetsIndex.Background">Download assets index (background)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadAssetsIndex.Side">Download assets index (side loader)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadCoreJar">Download core JAR</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadFabricApi">Download Fabric API</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadGameLibraries">Download game libraries</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadGameLibrariesSide">Download game libraries (side loader)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadInstanceJson">Download instance JSON</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadLabyModClientJson">Download LabyMod client JSON</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadLegacyFabricApi">Download Legacy Fabric API</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadLibraries">Download libraries</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadLibraries.MainLoader">Download libraries (main loader)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadLiteLoaderLibraries">Download LiteLoader libraries</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadLoaderLibraries">Download {0} libraries</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadLoaderLibraries.Side">Download {0} libraries (side loader)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadLoaderMainFile">Download {0} main file</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadMainFile">Download main file</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadOptiFabric">Download OptiFabric</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadOptiFineLibraries">Download OptiFine libraries</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadOptiFineMainFile">Download OptiFine main file</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadServerFile">Download server file</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadVanillaAndLabyModLibrariesSide">Download Vanilla and LabyMod libraries (side loader)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadVanillaAssets">Download Vanilla assets</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadVanillaLibraries.Side">Download Vanilla libraries (side loader)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.FabricInstallerDownload">Fabric {0} installer download</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ForgelikeDownload">{0} download</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ForgelikeInstallerFilter">{0} installer (*.{1})|*.{1}</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.GetLoaderLibraries">Obtain {0} libraries</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.InstallForge.MethodA">Install Forge (method A)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.InstallForge.MethodB">Install Forge (method B)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.InstallForgeType">Install {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.InstallGame">Install game</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.InstallLiteLoader">Install LiteLoader</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.InstallOptiFine.MethodA">Install OptiFine (method A)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.InstallOptiFine.MethodB">Install OptiFine (method B)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.LabyModInstallerDownload">LabyMod installer download</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.LegacyFabricInstallerDownload">Legacy Fabric {0} installer download</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.LiteLoaderDownload">LiteLoader {0} download</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.LiteLoaderInstallerDownload">LiteLoader {0} installer download</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.LoaderDownloadCombo">{0} {1} download</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.LoaderInstallerDownload">Download {0} {1}</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.MinecraftDownload">Minecraft {0} download</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.MinecraftServerDownload">Minecraft {0} server download</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ObtainFabricMainFileUrl">Obtain Fabric main file URI</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ObtainLabyModClientUrl">Obtain LabyMod client file URI</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ObtainLegacyFabricMainFileUrl">Obtain Legacy Fabric main file URI</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ObtainLoaderDetails">Obtain {0} details</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ObtainOptiFineDownloadUrl">Obtain OptiFine URI</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ObtainOptiFineUrl">Obtain OptiFine URI</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ObtainVanillaJsonUrl">Obtain Vanilla JSON URL</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.OptiFineDownload">OptiFine {0} download</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.PrepareLoaderDownload">Prepare to download {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.StartLiteLoaderDependencyDownload">Start LiteLoader dependency download</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.UpdateAssetsIndex.Background">Update assets index (background)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.VanillaJsonDownload">Download Vanilla JSON</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.VanillaLibrariesDownload">Download Vanilla libraries</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.WaitVanillaDownload">Wait for Vanilla download</sys:String>
<!-- Minecraft.Error -->
<sys:String x:Key="Minecraft.Error.AssetIndexNotFound">Asset index not found</sys:String>
<sys:String x:Key="Minecraft.Error.CannotReadInstanceFolder">Cannot read instance folder, possibly due to permissions ({0})</sys:String>
<sys:String x:Key="Minecraft.Error.DependencyRecursion">Version dependency recursion: {0}</sys:String>
<sys:String x:Key="Minecraft.Error.InitInstanceJsonFailed">Failed to initialize instance JSON ({0})</sys:String>
<sys:String x:Key="Minecraft.Error.InstanceJsonNotFound">Instance JSON file not found: {0}</sys:String>
<sys:String x:Key="Minecraft.Error.NoAssetIndexInfo">This instance has no asset index information</sys:String>
<sys:String x:Key="Minecraft.Error.QuiltUnsupported">Quilt does not support Minecraft {0}</sys:String>
<sys:String x:Key="Minecraft.Error.Unrecognizable">Unrecognizable: {0}</sys:String>
<sys:String x:Key="Minecraft.Error.UpdateNotify">Failed to send Minecraft update notification. ({0})</sys:String>
<sys:String x:Key="Minecraft.Error.VersionNumberEmpty">Cannot get version number, result is empty</sys:String>
<sys:String x:Key="Minecraft.Error.VersionNumberFetchFailed">Failed to fetch version number: {0}</sys:String>
<!-- Minecraft.Fool -->
<sys:String x:Key="Minecraft.Fool.Tag.Blue">(Blue version)</sys:String>
<sys:String x:Key="Minecraft.Fool.Tag.Purple">(Purple version)</sys:String>
<sys:String x:Key="Minecraft.Fool.Tag.Red">(Red version)</sys:String>
<!-- Minecraft.Fool.Description -->
<sys:String x:Key="Minecraft.Fool.Description.2013">2013 | This update, planned in secret for two years, takes the game to new heights!</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2015">2015 | As an all-ages game, we need peace, love, and hugs.</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2016">2016 | It's time to bring modern technology to Minecraft!</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2019">2019 | We found this masterpiece from 1994 in the basement ruins!</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2020">2020 | We added 20 billion new dimensions, turning infinite imagination into reality!</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2022">2022 | One block at a time! Embrace new mining, crafting and riding gameplay!</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2023">2023 | Research shows: players love making choices—the more the better!</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2024">2024 | Poisonous potatoes have always been overlooked and underestimated, so we supercharged them!</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2025">2025 | You can craft anything—including crafting your world!</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2026">2026 | Who needs an inventory? Let the blocks follow you!</sys:String>
<!-- Minecraft.Launch -->
<sys:String x:Key="Minecraft.Launch.AccountVerification.Back">Back</sys:String>
<sys:String x:Key="Minecraft.Launch.AccountVerification.Demo">Demo</sys:String>
<sys:String x:Key="Minecraft.Launch.AccountVerification.Message">You must log in with a Microsoft account to launch the game!</sys:String>
<sys:String x:Key="Minecraft.Launch.AccountVerification.Purchase">Purchase</sys:String>
<sys:String x:Key="Minecraft.Launch.AccountVerification.Title">Microsoft login</sys:String>
<sys:String x:Key="Minecraft.Launch.Cancelled">Launch canceled!</sys:String>
<sys:String x:Key="Minecraft.Launch.DemoMode">The game will launch in demo mode!</sys:String>
<sys:String x:Key="Minecraft.Launch.Donate.Decline">Pardon my rejection</sys:String>
<sys:String x:Key="Minecraft.Launch.Donate.Message">PCL CE has launched the game {0} times for you!&#xA;If PCL CE has been useful, maybe consider supporting the original PCL author...&#xA;Without everyone's support, it's hard for PCL to maintain updates for years while staying free and ad-free!</sys:String>
<sys:String x:Key="Minecraft.Launch.Donate.Support">Support!</sys:String>
<sys:String x:Key="Minecraft.Launch.Donate.Title">{0} launches!</sys:String>
<sys:String x:Key="Minecraft.Launch.ExportScript.Cancelled">Exporting launch script canceled!</sys:String>
<sys:String x:Key="Minecraft.Launch.ExportScript.Success">Launch script exported successfully!</sys:String>
<sys:String x:Key="Minecraft.Launch.PurchaseHint.Later">Nah, maybe next time</sys:String>
<sys:String x:Key="Minecraft.Launch.PurchaseHint.Message">It looks like you haven't purchased the game...&#xA;If you think Minecraft is good, consider purchasing it to support the developers... Don't keep freeloading.&#xA;&#xA;This prompt will no longer appear after you verify a valid Microsoft account!</sys:String>
<sys:String x:Key="Minecraft.Launch.PurchaseHint.Purchase">Purchase!</sys:String>
<sys:String x:Key="Minecraft.Launch.PurchaseHint.Title">Purchase the game?</sys:String>
<sys:String x:Key="Minecraft.Launch.Success">{0} launched successfully!</sys:String>
<!-- Minecraft.Launch.Error -->
<sys:String x:Key="Minecraft.Launch.Error.AlreadyLaunching">A game is already launching!</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.CancelProcess">Failed to cancel launch and terminate process</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.CannotConnectAuthServer">Unable to connect to third-party login server. ({0})</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.CannotConnectAuthServerWithDetail">Unable to connect to third-party login server ({0}).&#xA;Details:</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.CannotLaunch">Cannot launch Minecraft: {0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.CustomCommand">Failed to execute custom command</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.DispatcherUnavailable">The launcher interface is unavailable and cannot run this action.</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.ExportScriptFailed">Failed to export the launch script. See the details below.</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.GpuSet">Failed to set GPU preference, Minecraft may run on the default GPU</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.InvalidState">Invalid state change: {0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.LaunchFailed">Minecraft failed to launch. See the details below.</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.MissingMainClass">Missing "mainClass" in version JSON!</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.NativesCorrupted">Unable to open the Natives file ({0}), the file may be corrupted. Please try relaunching the game.</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.NoJava">No Java available, launch canceled!</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.OperationFailed">A Minecraft launch operation failed. See the details below.</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.OptiFineAutoJoinWarning">OptiFine may be incompatible with auto-join server, potentially causing missing textures or even game crashes!</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.PrioritySet">Failed to set process priority</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.Proxy">Failed to add proxy info, giving up</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.RefreshInfo">Failed to refresh launch info</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.SpecialMessage.WithDetail">Launch failed.&#xA;&#xA;Error message:&#xA;{0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.UpdateOptionsFailed">Failed to update options.txt. See the details below.</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.UpdateProfilesFailed">Failed to update launcher_profiles.json. See the details below.</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.WatcherOperationFailed">Minecraft runtime monitoring failed. See the details below.</sys:String>
<!-- Minecraft.Launch.Java -->
<sys:String x:Key="Minecraft.Launch.Java.AutoDownload.Action">Download automatically</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.AutoDownload.Message">PCL CE could not find {0}. Would you like PCL CE to download it automatically?&#xA;If you have already installed {0}, you can import it manually under Settings → Launch options → Game Java.</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.AutoDownload.Title">Download Java automatically?</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Compatibility.CheckFailed">Failed to check Java compatibility. See the details below.</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Compatibility.GlobalSelectedOutOfRange">The globally selected Java ({0}) is outside the required range [{1}, {2}] and may cause the game to crash.</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Compatibility.InstanceSelectedOutOfRange">The Java selected for this instance ({0}) is outside the required range [{1}, {2}] and may cause the game to crash.</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Compatibility.NoMaximum">No maximum</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Compatibility.NoMinimum">No minimum</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Compatibility.RelativePathSelectedOutOfRange">The Java selected through this instance's relative path ({0}) is outside the required range [{1}, {2}] and may cause the game to crash.</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.InstallManuallyHint">Please search and install it manually.</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.NeedJava7">You need to install Java 7 to launch this version.&#xA;Please search and install Java 7 manually, then re-search or import it under "Settings → Launch options → Game Java".</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.NeedJava8U141OrLater">You need to install Java 8u141 or later to launch this version.&#xA;Please search and install it manually, then re-search or import it under "Settings → Launch options → Game Java".</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.NeedJava8U141ToU320">You need to install Java 8u141 ~ 8u320 to launch this version.&#xA;Please search and install it manually, then re-search or import it under "Settings → Launch options → Game Java".</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.NeedLegacyJavaFixerOrJava7">You need to install the LegacyJavaFixer mod, or install Java 7 to launch this version.&#xA;Please search and install Java 7 manually, then re-search or import it under "Settings → Launch options → Game Java".</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.NotFound.Manual.Message">PCL CE could not find {0}.&#xA;Please install {0} manually, then re-search or import it under Settings → Launch options → Game Java.</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.NotFound.Title">Java not found</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Task.Download">Download Java</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Task.DownloadFiles">Download Java files</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Task.GetDownloadInfo">Get Java download information</sys:String>
<!-- Minecraft.Launch.Login -->
<sys:String x:Key="Minecraft.Launch.Login.Continue">Continue</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Error.Input">Failed to get login input information.</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Failed">Login failed</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Hint">Login hint</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.IKnow">I see</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.PasswordRequired.Message">During login, please select {0}Other Sign-in Methods{1}, then select {0}Use My Password{1}.&#xA;If this option is not available, please select {0}Set Password{1}, then log in after setting it up.</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.PasswordRequired.Relogin">Log in again</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.PasswordRequired.SetPassword">Set password</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.PasswordRequired.Title">Password required</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.RefreshAccountFailed.Message">The launcher encountered a network error while trying to refresh account information.&#xA;You can cancel, check your network, and try again, or ignore the error and continue launching, but some servers may not be playable.</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.RefreshAccountFailed.Title">Failed to refresh account information</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Register">Register</sys:String>
<!-- Minecraft.Launch.Login.Auth -->
<sys:String x:Key="Minecraft.Launch.Login.Auth.DetailPrefix">Details:</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.FailedTitle">Third-party login failed</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.InvalidProfile">Selected profile {0} is invalid!</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.LoginFailed">Login failed: {0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.LoginFailed.WithDetail">Third-party login failed.&#xA;&#xA;Details:&#xA;{0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.NetworkFailed.Message">Third-party login failed. Please check your network connection.</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.NetworkFailed.WithDetail">Third-party login failed. Please check your network connection.&#xA;&#xA;Details:&#xA;{0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.NoProfile">$You have not created a profile. Please create one first!</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.NoProfileCannotSwitch">You have not created a profile, so you cannot switch profiles!</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.OnlyOneProfile">There is only one profile in your account, so you cannot switch profiles!</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.RefreshFailed">Login refresh failed</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.RefreshFailed.WithDetail">Login refresh failed.&#xA;&#xA;Details:&#xA;{0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.SecondRefreshFailed">Second login refresh failed</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.SelectProfile">Select profile</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.Timeout.DetailMessage">$Login failed: Connection to login server timed out.&#xA;Please check your network connection!&#xA;Details:</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.Timeout.Message">Login failed: Connection to login server timed out.&#xA;Please check your network connection!</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.Timeout.WithDetail">Login failed because the connection to the login server timed out.&#xA;Please check your network connection.&#xA;&#xA;Details:&#xA;{0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.ValidationFailed.WithDetail">Login validation failed.&#xA;&#xA;Details:&#xA;{0}</sys:String>
<!-- Minecraft.Launch.Login.Microsoft -->
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.AbnormalIp">Abnormal login attempts from this IP.&#xA;Please check your network settings or try again later.</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.Banned">This account appears to have been banned by Microsoft and cannot log in.</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.ChangeBirthDate.Message">Please modify the account's date of birth on the opened webpage (change to at least 18 years old).&#xA;Wait one minute after the modification is successful, then return to PCL CE and you can log in normally!</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.ChangeBirthDate.SupportMessage">Please modify the account's date of birth on the opened webpage (change to at least 18 years old).&#xA;Wait one minute after the modification is successful, then return to PCL CE and you can log in normally!</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.CreateProfile.Button">Create profile</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.CreateProfile.Message">Please create a Minecraft player profile first, then log in again.</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.NotPurchased">Unable to get account information. This account may not have purchased Minecraft: Java Edition, or its Xbox Game Pass may have expired.</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.ProfileAlreadyAdded">You have already added this profile...</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.PurchaseMinecraft">Purchase Minecraft</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.RegionBlocked">Microsoft account login is not available in your country or region.&#xA;Please check your network settings or try again later.</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.TooManyRequests">Too many login attempts. Please wait a few minutes and try again!</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.Underage.AgeOver13">13 or older</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.Underage.AgeUnder13">12 or younger</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.Underage.Message">The account is underage. You need to modify the date of birth before logging in.&#xA;Is the age currently set on this account over 13?</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.XboxNotRegistered">You have not registered an Xbox account. Please register one first and then log in.</sys:String>
<!-- Minecraft.Launch.Precheck -->
<sys:String x:Key="Minecraft.Launch.Precheck.AuthServerMismatch">The third-party login server used by the current profile does not match the one required by this instance. Please use a matching profile!</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.Failed.WithDetail">Launch precheck failed: {0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.InstanceError">Minecraft has a problem: {0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.InvalidPathChars">Game path cannot contain ! or ; ({0})</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.NoInstance">No Minecraft instance selected!</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.NoProfile">Please select a profile before launching!</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.NonAsciiPath.Back">Back</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.NonAsciiPath.Continue">Continue</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.NonAsciiPath.Message">The path of instance "{0}" contains characters that may affect the game (non-ASCII characters). Do you still want to launch?&#xA;&#xA;If you are unsure, you can choose "Continue" first, and try modifying the game path if the game crashes shortly after launch.</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.NonAsciiPath.Title">Game path check</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.RequireMicrosoft">This instance requires Microsoft login. Please use a Microsoft login profile!</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.RequireMicrosoftOrThirdParty">This instance requires Microsoft or third-party login. Please use a matching profile!</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.RequireThirdParty">This instance requires third-party login. Please use a third-party login profile!</sys:String>
<!-- Minecraft.Launch.Stage -->
<sys:String x:Key="Minecraft.Launch.Stage.CompleteFiles">Complete files</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.CustomCommand">Execute custom command</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.End">End processing</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.ExtractNatives">Extract natives</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.GetArguments">Get launch arguments</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.GetJava">Get Java</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.Login">Login</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.PreLaunch">Pre-launch processing</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.Root">Minecraft launch</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.StartProcess">Start process</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.WaitWindow">Wait for appearance</sys:String>
<!-- Music -->
<sys:String x:Key="Music.Hint.Cleared">Background music cleared!</sys:String>
<sys:String x:Key="Music.Hint.Playing">Playing: {0}</sys:String>
<sys:String x:Key="Music.Hint.Refreshed">Background music refreshed: {0}</sys:String>
<sys:String x:Key="Music.Hint.Replaying">Replaying: {0}</sys:String>
<sys:String x:Key="Music.ToolTip.Paused.Multiple">Paused: {0}&#xA;Left-click to resume. Right-click to play the next track.</sys:String>
<sys:String x:Key="Music.ToolTip.Paused.Single">Paused: {0}&#xA;Left-click to resume. Right-click to replay from the beginning.</sys:String>
<sys:String x:Key="Music.ToolTip.Playing.Multiple">Playing: {0}&#xA;Left-click to pause. Right-click to play the next track.</sys:String>
<sys:String x:Key="Music.ToolTip.Playing.Single">Playing: {0}&#xA;Left-click to pause. Right-click to replay from the beginning.</sys:String>
<!-- Music.Error -->
<sys:String x:Key="Music.Error.DeviceBusy">The audio device is being used by another program. Close the program using it, then restart PCL CE to restore music playback.</sys:String>
<sys:String x:Key="Music.Error.DeviceChanged">The audio device has changed. Restart PCL CE to restore music playback.</sys:String>
<sys:String x:Key="Music.Error.NoAvailable">No music is available to play.</sys:String>
<sys:String x:Key="Music.Error.NotDetected">No usable background music was found.</sys:String>
<sys:String x:Key="Music.Error.NotStarted">Music playback has not started.</sys:String>
<sys:String x:Key="Music.Error.OperationFailed">Failed to initialize or refresh background music. See the details below.</sys:String>
<sys:String x:Key="Music.Error.PlaybackFailed">Failed to play {0}.</sys:String>
<sys:String x:Key="Music.Error.PropertyChangeUnsupported">Failed to play {0}: PCL CE does not support files that change audio properties during playback.</sys:String>
<sys:String x:Key="Music.Error.UnsupportedFormat">Failed to play {0}: PCL CE may not support this format. Convert it to .wav, .mp3, or .flac.</sys:String>
<!-- Select -->
<sys:String x:Key="Select.Error.OperationFailed">An instance selection operation failed. See the details below.</sys:String>
<!-- Select.Folder -->
<sys:String x:Key="Select.Folder.AccessDenied">Failed to add folder: PCL CE does not have permission to access this folder!</sys:String>
<sys:String x:Key="Select.Folder.AddExisting.Title">Add an existing folder</sys:String>
<sys:String x:Key="Select.Folder.AddExisting.ToolTip">Add an existing Minecraft folder to the list.</sys:String>
<sys:String x:Key="Select.Folder.AddOrImport">Add or import</sys:String>
<sys:String x:Key="Select.Folder.Added">The folder {0} has been added!</sys:String>
<sys:String x:Key="Select.Folder.AlreadyInList">This folder already exists on the list!</sys:String>
<sys:String x:Key="Select.Folder.CannotAddWhileDownloading">The game folder cannot be added while the download task is in progress!</sys:String>
<sys:String x:Key="Select.Folder.CannotCreateWhileDownloading">The game folder cannot be created while the download task is in progress!</sys:String>
<sys:String x:Key="Select.Folder.Cleanup.Keep">Keep</sys:String>
<sys:String x:Key="Select.Folder.Cleanup.Message">Do you want to clean up the PCL CE config files in this folder?&#xA;This includes settings for each instance (such as custom icons, third-party login options, etc.). It has no effect on the instance itself.</sys:String>
<sys:String x:Key="Select.Folder.Cleanup.Title">Profile clean-up</sys:String>
<sys:String x:Key="Select.Folder.Clear">Clear</sys:String>
<sys:String x:Key="Select.Folder.Clear.Confirm">Clear</sys:String>
<sys:String x:Key="Select.Folder.Clear.FinalWarning">Non-Minecraft files stored in the folder will also be cleared!&#xA;Continuing will cause all files in this folder to be permanently lost.&#xA;Please confirm carefully before continuing!&#xA;Target folder: {0}&#xA;&#xA;This is the last warning!</sys:String>
<sys:String x:Key="Select.Folder.Clear.FirstWarning">Are you sure you want to clear this folder?&#xA;Target folder: {0}&#xA;&#xA;All saves and other files in this folder will be permanently lost and unrecoverable!</sys:String>
<sys:String x:Key="Select.Folder.Clear.InProgress">Clearing folder {0}...</sys:String>
<sys:String x:Key="Select.Folder.Clear.Success">The folder {0} has been cleared!</sys:String>
<sys:String x:Key="Select.Folder.CreateNew.Title">Create a new folder</sys:String>
<sys:String x:Key="Select.Folder.CreateNew.ToolTip">Create a new ".minecraft" folder at the location of PCL CE.</sys:String>
<sys:String x:Key="Select.Folder.CreateSuccess">Create a new ".minecraft" folder successfully!</sys:String>
<sys:String x:Key="Select.Folder.CurrentFolder">Current folder</sys:String>
<sys:String x:Key="Select.Folder.Delete.Confirm">Delete</sys:String>
<sys:String x:Key="Select.Folder.Delete.FinalWarning">Non-Minecraft files stored in the folder will also be deleted!&#xA;Continuing the deletion will cause all files in this folder to be permanently lost.&#xA;Please confirm carefully before continuing!&#xA;Target folder: {0}&#xA;&#xA;This is the last warning!</sys:String>
<sys:String x:Key="Select.Folder.Delete.FirstWarning">Are you sure you want to delete this folder?&#xA;Target folder: {0}&#xA;&#xA;All saves and other files in this folder will be permanently lost and unrecoverable!</sys:String>
<sys:String x:Key="Select.Folder.Delete.InProgress">Deleting folder {0}...</sys:String>
<sys:String x:Key="Select.Folder.Delete.Success">The folder {0} has been deleted!</sys:String>
<sys:String x:Key="Select.Folder.Delete.WarningTitle">Deletion warning</sys:String>
<sys:String x:Key="Select.Folder.ImportModpack.Title">Import a modpack</sys:String>
<sys:String x:Key="Select.Folder.ImportModpack.ToolTip">Install the modpack in the selected Minecraft folder.</sys:String>
<sys:String x:Key="Select.Folder.InputDisplayName.Message">Enter the name of the folder that shows on the list on the left column.</sys:String>
<sys:String x:Key="Select.Folder.InputDisplayName.Title">Enter a display name</sys:String>
<sys:String x:Key="Select.Folder.Invalid">Invalid Minecraft folder: {0}</sys:String>
<sys:String x:Key="Select.Folder.Invalid.WithDetail">Invalid Minecraft folder: {0}&#xA;&#xA;Details:&#xA;{1}</sys:String>
<sys:String x:Key="Select.Folder.InvalidMessage">Invalid Minecraft folder</sys:String>
<sys:String x:Key="Select.Folder.InvalidPathChars">Minecraft folder paths cannot contain exclamation marks (!) or semicolons (;)!</sys:String>
<sys:String x:Key="Select.Folder.InvalidTitle">Minecraft folder unavailable</sys:String>
<sys:String x:Key="Select.Folder.ListTitle">Folder list</sys:String>
<sys:String x:Key="Select.Folder.MoveDown">Move down</sys:String>
<sys:String x:Key="Select.Folder.MoveUp">Move up</sys:String>
<sys:String x:Key="Select.Folder.NameUpdated">The folder has been renamed to {0}!</sys:String>
<sys:String x:Key="Select.Folder.OfficialLauncherFolder">Official launcher folder</sys:String>
<sys:String x:Key="Select.Folder.RemoveFromList">Remove from list</sys:String>
<sys:String x:Key="Select.Folder.RemoveSuccess">The folder {0} has been removed from the list!</sys:String>
<sys:String x:Key="Select.Folder.Rename">Rename</sys:String>
<sys:String x:Key="Select.Folder.Rename.Title">Enter a new name</sys:String>
<sys:String x:Key="Select.Folder.RestoreName">Restore name</sys:String>
<sys:String x:Key="Select.Folder.RestoreSuccess">The folder name has been restored!</sys:String>
<sys:String x:Key="Select.Folder.SwitchBlockedByDownload">The game folder cannot be switched while the download task is in progress!</sys:String>
<!-- Select.Folder.Error -->
<sys:String x:Key="Select.Folder.Error.Add">Failed to add folder. ({0})</sys:String>
<sys:String x:Key="Select.Folder.Error.AddNew">Failed to add a new folder to the folder list.</sys:String>
<sys:String x:Key="Select.Folder.Error.Cleanup">Failed to clean up leftover ignored items. ({0})</sys:String>
<sys:String x:Key="Select.Folder.Error.DragDrop">Failed to sort folders by drag-and-drop.</sys:String>
<sys:String x:Key="Select.Folder.Error.InstallPack">Failed to install the modpack automatically: {0}</sys:String>
<sys:String x:Key="Select.Folder.Error.Load">Failed to load Minecraft folder list.</sys:String>
<sys:String x:Key="Select.Folder.Error.Operate">Failed to {0} folder {1}.</sys:String>
<sys:String x:Key="Select.Folder.Error.Remove">Failed to remove game folder from list.</sys:String>
<sys:String x:Key="Select.Folder.Error.Rename">Failed to rename the folder.</sys:String>
<!-- Select.Instance -->
<sys:String x:Key="Select.Instance.Delete.ConfirmMessage">Are you sure you want to delete the instance {0}?</sys:String>
<sys:String x:Key="Select.Instance.Delete.ConfirmPermanentMessage">Are you sure you want to permanently delete the instance {0}?</sys:String>
<sys:String x:Key="Select.Instance.Delete.ConfirmTitle">Instance deletion</sys:String>
<sys:String x:Key="Select.Instance.Delete.IsolatedWarning">Since the instance isolation is enabled for this instance, the corresponding saves, resource packs, mods, and other files of this instance will also be deleted!</sys:String>
<sys:String x:Key="Select.Instance.Delete.PermanentSuccess">The instance {0} has been permanently deleted!</sys:String>
<sys:String x:Key="Select.Instance.Delete.RecycleBinSuccess">The instance {0} has been moved to the recycle bin!</sys:String>
<sys:String x:Key="Select.Instance.Empty.Download">Download</sys:String>
<sys:String x:Key="Select.Instance.Empty.Message">No instances found. Please download an instance in any game version first.&#xA;If there is an existing instance, click "Add an existing folder" on the list on the left, and select the ".minecraft" folder to import it.</sys:String>
<sys:String x:Key="Select.Instance.Empty.Title">No instances available</sys:String>
<sys:String x:Key="Select.Instance.EmptySearch.Title">No matching instances</sys:String>
<sys:String x:Key="Select.Instance.Error.Classify">Failed while converting the instance list to display.</sys:String>
<sys:String x:Key="Select.Instance.Error.Delete">Failed to delete the instance {0}.</sys:String>
<sys:String x:Key="Select.Instance.Error.IconLoad">Failed to load instance icon.</sys:String>
<sys:String x:Key="Select.Instance.Error.ListLoad">Failed to load .minecraft instance list.</sys:String>
<sys:String x:Key="Select.Instance.Error.Load">Failed to load instance. ({0})</sys:String>
<sys:String x:Key="Select.Instance.Error.UiUpdate">Failed to render instance list UI.</sys:String>
<sys:String x:Key="Select.Instance.Favorite">Favorite</sys:String>
<sys:String x:Key="Select.Instance.Hidden.EmptyMessage">No instances are hidden. You can select "Hidden from list" in "Properties → Personalization → Category" to hide the instance.&#xA;Press "F11" again to exit the hidden instance viewing mode.</sys:String>
<sys:String x:Key="Select.Instance.Hidden.EmptySearchTitle">No matching hidden instances</sys:String>
<sys:String x:Key="Select.Instance.Hidden.EmptyTitle">No hidden instances</sys:String>
<sys:String x:Key="Select.Instance.Loading">Getting instance list...</sys:String>
<sys:String x:Key="Select.Instance.OpenFolder">Folder</sys:String>
<sys:String x:Key="Select.Instance.Search.EmptyInput">Please enter search content.</sys:String>
<sys:String x:Key="Select.Instance.Search.Hint">Search game instances</sys:String>
<sys:String x:Key="Select.Instance.Search.NoHiddenResult">No hidden instances matching "{0}" were found.</sys:String>
<sys:String x:Key="Select.Instance.Search.NoResult">No instances matching "{0}" were found.</sys:String>
<sys:String x:Key="Select.Instance.Search.ToolTip">Enter instance name or [game version] [mod loader] to search.</sys:String>
<sys:String x:Key="Select.Instance.Settings">Properties</sys:String>
<sys:String x:Key="Select.Instance.Unfavorite">Unfavorite</sys:String>
<!-- Select.Instance.Card -->
<sys:String x:Key="Select.Instance.Card.AprilFools">April Fools'</sys:String>
<sys:String x:Key="Select.Instance.Card.Cleanroom">Cleanroom</sys:String>
<sys:String x:Key="Select.Instance.Card.Error">Error</sys:String>
<sys:String x:Key="Select.Instance.Card.Fabric">Fabric</sys:String>
<sys:String x:Key="Select.Instance.Card.Favorites">Favorites</sys:String>
<sys:String x:Key="Select.Instance.Card.Forge">Forge</sys:String>
<sys:String x:Key="Select.Instance.Card.Hidden">Hidden</sys:String>
<sys:String x:Key="Select.Instance.Card.LabyMod">LabyMod</sys:String>
<sys:String x:Key="Select.Instance.Card.LessUsed">Less-used</sys:String>
<sys:String x:Key="Select.Instance.Card.LiteLoader">LiteLoader</sys:String>
<sys:String x:Key="Select.Instance.Card.Modable">Mod-installable</sys:String>
<sys:String x:Key="Select.Instance.Card.NeoForge">NeoForge</sys:String>
<sys:String x:Key="Select.Instance.Card.Quilt">Quilt</sys:String>
<sys:String x:Key="Select.Instance.Card.Regular">Regular</sys:String>
<!-- Select.Instance.Description -->
<sys:String x:Key="Select.Instance.Description.AprilFools">April Fools' {0}</sys:String>
<sys:String x:Key="Select.Instance.Description.ExperimentalSnapshot">Experimental snapshot {0}</sys:String>
<sys:String x:Key="Select.Instance.Description.ExperimentalSnapshot.Pending">Experimental snapshot</sys:String>
<sys:String x:Key="Select.Instance.Description.NeedInherit">{0} needs to be installed as a parent instance.</sys:String>
<sys:String x:Key="Select.Instance.Description.NoPermission">PCL CE does not have permission to access this folder. Please run PCL CE as administrator.</sys:String>
<sys:String x:Key="Select.Instance.Description.NotFound">Instance {0} not found.</sys:String>
<sys:String x:Key="Select.Instance.Description.NotLoaded">This instance has not been loaded. Please report this issue to the developers.</sys:String>
<sys:String x:Key="Select.Instance.Description.Old">Before release</sys:String>
<sys:String x:Key="Select.Instance.Description.PreRelease">Pre-release {0}</sys:String>
<sys:String x:Key="Select.Instance.Description.Release">Release {0}</sys:String>
<sys:String x:Key="Select.Instance.Description.Release.Unknown">Release</sys:String>
<sys:String x:Key="Select.Instance.Description.ReleaseCandidate">Release candidate {0}</sys:String>
<sys:String x:Key="Select.Instance.Description.ReportUnknownError">An unknown error occurred. Please report this issue to the author.</sys:String>
<sys:String x:Key="Select.Instance.Description.Snapshot">Snapshot {0}</sys:String>
<sys:String x:Key="Select.Instance.Description.Snapshot.Unknown">Snapshot</sys:String>
<sys:String x:Key="Select.Instance.Description.UnknownError">Unknown error</sys:String>
<sys:String x:Key="Select.Instance.Description.UnknownMcVersion">PCL CE cannot recognize the Minecraft version number of this version.</sys:String>
<!-- Setup -->
<sys:String x:Key="Setup.Error.OperationFailed">A settings operation failed. See the details below.</sys:String>
<!-- Setup.About -->
<sys:String x:Key="Setup.About.Community.Info">The development team of Plain Craft Launcher Community Edition!</sys:String>
<sys:String x:Key="Setup.About.Community.Name">PCL Community</sys:String>
<sys:String x:Key="Setup.About.Contributors.Title">Contributors</sys:String>
<sys:String x:Key="Setup.About.Contributors.ViewMore">View more</sys:String>
<sys:String x:Key="Setup.About.Error.LoadContributorsFailed">Failed to load contributor information</sys:String>
<sys:String x:Key="Setup.About.GitHubHome">GitHub page</sys:String>
<sys:String x:Key="Setup.About.Licenses.Title">Licenses and copyrights</sys:String>
<sys:String x:Key="Setup.About.Licenses.ViewLicense">License document</sys:String>
<sys:String x:Key="Setup.About.Licenses.ViewWebsite">Source website</sys:String>
<sys:String x:Key="Setup.About.OriginalAuthor.Info">The original author of Plain Craft Launcher!</sys:String>
<sys:String x:Key="Setup.About.OriginalAuthor.Name">LTCat (龙腾猫跃)</sys:String>
<sys:String x:Key="Setup.About.SourceCode">Source code</sys:String>
<sys:String x:Key="Setup.About.SponsorOriginalAuthor">Sponsor original author</sys:String>
<sys:String x:Key="Setup.About.Title">About</sys:String>
<sys:String x:Key="Setup.About.UpstreamLegal.CopyrightText">Copyright © 龙腾猫跃 2016. All Rights Reserved.&#xD;Computer Software Copyright Registration No.: 2020SR0875133&#xD;Report violations to: hexdragon@vip.qq.com&#xD;NOT AN OFFICIAL MINECRAFT PRODUCT. NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.</sys:String>
<sys:String x:Key="Setup.About.UpstreamLegal.OpenSource">Source code</sys:String>
<sys:String x:Key="Setup.About.UpstreamLegal.PrivacyText">The User Agent of this software includes the launcher version number to count the number of users on each version.&#xD;Other than this, this software will not upload any other information to the developers.</sys:String>
<sys:String x:Key="Setup.About.UpstreamLegal.TermsAndDisclaimer">ToS &amp; Disclaimer</sys:String>
<sys:String x:Key="Setup.About.UpstreamLegal.Title">Upstream legal information</sys:String>
<sys:String x:Key="Setup.About.Version.Info">Current version: %VERSION% (%BRANCH%, %VERSIONCODE%, %COMMIT_HASH%)</sys:String>
<!-- Setup.About.EasterEgg -->
<sys:String x:Key="Setup.About.EasterEgg.Bored.Message">Are you really that bored?</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Bored.No">Not really</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Bored.Response">Then why are you still clicking... I don't get it.</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Bored.Title">Coo coo coo?</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Bored.Yes">Yes</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.ClickDisabled">Still clicking? Click again and I'll disable clicking...</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Encouragement">Uh, keep going, uh...</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.HiddenTheme">The hidden theme "Chaos Yellow" has been unlocked... wait, this is PCL CE, probably not...</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.NiceClick">Is this fun to click...</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.NothingBehind">There's nothing behind this!</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.StillClicking">Still clicking?</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.StillStaring">How are you still so bored?</sys:String>
<!-- Setup.About.EasterEgg.Tired -->
<sys:String x:Key="Setup.About.EasterEgg.Tired.Exhausted">I'm exhausted</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.FinallyGiveUp">Alright... but there really is nothing behind this, no need to keep clicking, seriously.</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.Message1">Aren't you tired?</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.Message2">Aren't you really, really tired?</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.Message3">Aren't you really, really, really tired?</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.NotTired">Not really tired</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.ReallyNotTired">Really really really not tired</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.StopClicking">Then stop clicking... There's really, really, really nothing behind this!</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.Title1">Friendly reminder</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.Title2">Super friendly reminder</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.Title3">SUPER SUPER FRIENDLY REMINDER</sys:String>
<!-- Setup.About.Legal -->
<sys:String x:Key="Setup.About.Legal.CommunityLobbyPrivacy">Community lobby privacy policy</sys:String>
<sys:String x:Key="Setup.About.Legal.CommunitySource">PCL CE source code</sys:String>
<sys:String x:Key="Setup.About.Legal.CopyrightText">Copyright © PCL Community &amp; LTCat (龙腾猫跃) 2026.&#xD;NOT AN OFFICIAL MINECRAFT PRODUCT. NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.</sys:String>
<sys:String x:Key="Setup.About.Legal.NatayarkTerms">Natayark OpenID ToS</sys:String>
<sys:String x:Key="Setup.About.Legal.OtherInfo">Other information</sys:String>
<sys:String x:Key="Setup.About.Legal.PrivacyNotice">Privacy statement</sys:String>
<sys:String x:Key="Setup.About.Legal.PrivacyText">The User Agent of this software includes the launcher version number to count the number of users on each version.&#xD;Other than this, this software will not upload any other information to the developers.&#xD;* Online and hardware/software survey features follow their own privacy policies, which are separate from this notice.</sys:String>
<sys:String x:Key="Setup.About.Legal.Title">Legal information</sys:String>
<!-- Setup.About.SpecialThanks -->
<sys:String x:Key="Setup.About.SpecialThanks.GoBlog">Blog</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Info.Bmclapi">Provided BMCLAPI mirror and Forge installation tool. Details at https://bmclapi.bangbang93.com</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Info.Cloud">Provided cloud services for PCL CE.</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Info.IconDesign">Designed and created the PCL CE icon.</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Info.LinkModule">Provided the multiplayer module!</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Info.Mcim">Provided MCIM community resource mirrors and image hosting services for the help center!</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Info.Mcmod">Provided Chinese translation of mods and more related information!</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Name.Bangbang93">bangbang93</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Name.EasyTier">EasyTier</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Name.Emperormummy">Emperormummy</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Name.Mcmod">MCMod (MC 百科)</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Name.Pysio">Pysio @ Akaere Network</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Name.Yunmoan">云默安 @ 至远光辉</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Name.Z0z0r4">z0z0r4</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.OpenMcmod">Website</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.OpenWebsite">Website</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.SponsorMirror">Sponsor mirror</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Title">Special thanks</sys:String>
<!-- Setup.Feedback -->
<sys:String x:Key="Setup.Feedback.ErrorPrompt.Submit.Action">Send feedback</sys:String>
<sys:String x:Key="Setup.Feedback.ErrorPrompt.Submit.Message">{0}&#xA;&#xA;Would you like to send feedback? Without a report, this problem may not be resolved.</sys:String>
<sys:String x:Key="Setup.Feedback.ErrorPrompt.Update.Message">{0}&#xA;&#xA;Updating PCL CE to the latest version may resolve this problem.</sys:String>
<sys:String x:Key="Setup.Feedback.Item.Submitter">Submitter: {0} ({1})</sys:String>
<sys:String x:Key="Setup.Feedback.Item.Type">Type: {0}</sys:String>
<sys:String x:Key="Setup.Feedback.Item.ViewDetail">Details</sys:String>
<sys:String x:Key="Setup.Feedback.LoadFailed">Unable to get content</sys:String>
<sys:String x:Key="Setup.Feedback.Loading">Loading feedback list...</sys:String>
<sys:String x:Key="Setup.Feedback.Reminder.Message">To report a bug, click Open folder and upload the log file containing the error from Launch-{0}-[number].log.&#xA;&#xA;Game crashes are usually unrelated to the launcher. Please do not submit launcher feedback only because the game crashed.</sys:String>
<sys:String x:Key="Setup.Feedback.Reminder.NotNeeded">Not needed</sys:String>
<sys:String x:Key="Setup.Feedback.Reminder.Title">Feedback reminder</sys:String>
<sys:String x:Key="Setup.Feedback.Submit.Description">Please check the feedback list for similar issues before submitting new feedback to avoid duplicate submissions.&#xA;Note: This page does not show all feedback. To view more, please go to GitHub.</sys:String>
<sys:String x:Key="Setup.Feedback.Submit.Open">New feedback</sys:String>
<sys:String x:Key="Setup.Feedback.Submit.Title">Submit feedback</sys:String>
<sys:String x:Key="Setup.Feedback.Unavailable.CheckFailed.Action">Check again</sys:String>
<sys:String x:Key="Setup.Feedback.Unavailable.CheckFailed.Message">Update checking failed, so feedback cannot be sent.&#xA;Connect to the internet, check for updates, confirm that the problem still occurs on the latest version, then send feedback.</sys:String>
<sys:String x:Key="Setup.Feedback.Unavailable.NotLatest.Action">Update</sys:String>
<sys:String x:Key="Setup.Feedback.Unavailable.NotLatest.Message">You are not using the latest version of PCL CE, so feedback cannot be sent.&#xA;Update PCL CE, confirm that the problem still occurs on the latest version, then send feedback.</sys:String>
<sys:String x:Key="Setup.Feedback.Unavailable.Title">Cannot send feedback</sys:String>
<sys:String x:Key="Setup.Feedback.Uncategorized">Uncategorized</sys:String>
<!-- Setup.Feedback.Status -->
<sys:String x:Key="Setup.Feedback.Status.Completed">Completed</sys:String>
<sys:String x:Key="Setup.Feedback.Status.Declined">Declined</sys:String>
<sys:String x:Key="Setup.Feedback.Status.Duplicate">Duplicate</sys:String>
<sys:String x:Key="Setup.Feedback.Status.Ignored">Ignored</sys:String>
<sys:String x:Key="Setup.Feedback.Status.Paused">Paused</sys:String>
<sys:String x:Key="Setup.Feedback.Status.Processing">Processing</sys:String>
<sys:String x:Key="Setup.Feedback.Status.UpNext">Up next</sys:String>
<sys:String x:Key="Setup.Feedback.Status.Waiting">Waiting</sys:String>
<sys:String x:Key="Setup.Feedback.Status.WaitingProcess">Awaiting processing</sys:String>
<!-- Setup.GameLink -->
<sys:String x:Key="Setup.GameLink.CliDebugOutput">Output CLI information to logs for debugging</sys:String>
<sys:String x:Key="Setup.GameLink.CliDebugOutput.ToolTip">Output EasyTier CLI information to the launcher log every 30 seconds for developer and community support investigation.&#xA;This option is for debugging only and is not recommended to keep enabled.</sys:String>
<sys:String x:Key="Setup.GameLink.EnableIPv6">Allow IPv6 communication</sys:String>
<sys:String x:Key="Setup.GameLink.EnableIPv6.ToolTip">Compared with IPv4, IPv6 makes P2P communication easier and can greatly improve the multiplayer experience. Do not disable it unless you have a specific reason.</sys:String>
<sys:String x:Key="Setup.GameLink.Error.ConfigChangeFailed">Failed to change configuration item.</sys:String>
<sys:String x:Key="Setup.GameLink.Error.InitFailed">Failed to initialize multiplayer settings</sys:String>
<sys:String x:Key="Setup.GameLink.Error.NetworkTestFailed">Failed to get network test results. See the details below.</sys:String>
<sys:String x:Key="Setup.GameLink.Initialized">Multiplayer settings have been initialized!</sys:String>
<sys:String x:Key="Setup.GameLink.LatencyFirstMode">Select the lowest-latency path instead of the shortest path</sys:String>
<sys:String x:Key="Setup.GameLink.LatencyFirstMode.ToolTip">When enabled, EasyTier will choose the path with the lowest latency rather than the path with the fewest routing hops.&#xA;Enabling this may reduce latency in some cases, but may increase packet loss.</sys:String>
<sys:String x:Key="Setup.GameLink.PortGuessNat">Perform port guessing for symmetric NAT</sys:String>
<sys:String x:Key="Setup.GameLink.PortGuessNat.ToolTip">Let EasyTier attempt port guessing for symmetric NAT (birthday paradox).&#xA;This may improve the multiplayer experience, but it may also cause issues in some cases.&#xA;Generally, keep it enabled.</sys:String>
<sys:String x:Key="Setup.GameLink.Protocol.Preference">Protocol preference</sys:String>
<sys:String x:Key="Setup.GameLink.Protocol.Tcp">TCP</sys:String>
<sys:String x:Key="Setup.GameLink.Protocol.Tcp.ToolTip">Let EasyTier prefer TCP communication, which usually provides better stability.</sys:String>
<sys:String x:Key="Setup.GameLink.Protocol.Udp">UDP</sys:String>
<sys:String x:Key="Setup.GameLink.Protocol.Udp.ToolTip">Let EasyTier prefer UDP communication, which usually provides lower latency.</sys:String>
<sys:String x:Key="Setup.GameLink.RestartRequired">A lobby restart is required for changes on this page to take effect.</sys:String>
<sys:String x:Key="Setup.GameLink.Username">Username</sys:String>
<sys:String x:Key="Setup.GameLink.Username.ToolTip">PCL CE will try to use this username for lobby display.&#xA;If left blank, the Natayark ID username will be used.&#xA;&#xA;In some cases, the Natayark ID username may still be displayed instead of the name set here.</sys:String>
<!-- Setup.GameLink.NetworkTest -->
<sys:String x:Key="Setup.GameLink.NetworkTest.Ipv6Status">IPv6 status: {0}</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.Ipv6Status.NotTested">IPv6: Not tested</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.NotTested">Not tested</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.Start">Start test</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.Supported">Supported</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.TcpNatType">TCP NAT type: {0}</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.TcpNatType.NotTested">TCP NAT type: Not tested</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.Testing">Testing...</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.Title">Network test</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.UdpNatType">UDP NAT type: {0}</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.UdpNatType.NotTested">UDP NAT type: Not tested</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.Unsupported">Unsupported</sys:String>
<!-- Setup.GameManage -->
<sys:String x:Key="Setup.GameManage.Error.InitFailed">Failed to initialize game management settings</sys:String>
<sys:String x:Key="Setup.GameManage.Initialized">Game management settings have been initialized!</sys:String>
<sys:String x:Key="Setup.GameManage.Source.File">File download source</sys:String>
<sys:String x:Key="Setup.GameManage.Source.PreferOfficialThenMirror">Prefer official sources, fallback to mirrors when slow</sys:String>
<sys:String x:Key="Setup.GameManage.Source.Title">Game resources</sys:String>
<sys:String x:Key="Setup.GameManage.Source.UseMirror">Prefer mirror sources</sys:String>
<sys:String x:Key="Setup.GameManage.Source.UseOfficial">Prefer official sources</sys:String>
<sys:String x:Key="Setup.GameManage.Source.Version">Version list source</sys:String>
<sys:String x:Key="Setup.GameManage.Source.Version.Mirror">Prefer mirror sources (recently released versions may be missing)</sys:String>
<!-- Setup.GameManage.Accessibility -->
<sys:String x:Key="Setup.GameManage.Accessibility.AutoLauncherLanguage">Automatically set to launcher language</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.GameLanguage">Game language</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.GameUpdateNotice">Game update notification</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.ReadClipboard">Recognize community resource links in clipboard</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.ReadClipboard.ToolTip">When enabled, PCL CE will read your clipboard and recognize resource links, then ask whether to navigate to the detail page.</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.ReleaseNotice">Release notification</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.ReleaseNotice.ToolTip">Show a notification when a Minecraft release is available</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.SnapshotNotice">Snapshot notification</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.SnapshotNotice.ToolTip">Show a notification when a Minecraft snapshot or pre-release is available</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.Title">Accessibility</sys:String>
<!-- Setup.GameManage.Community -->
<sys:String x:Key="Setup.GameManage.Community.AutoInstallDependencies">Auto-check and install required dependencies when downloading mods</sys:String>
<sys:String x:Key="Setup.GameManage.Community.AutoInstallDependencies.ToolTip">When installing a mod, if missing required dependencies are detected, a list will be shown for confirmation before auto-downloading and installing them (can be cancelled during confirmation)</sys:String>
<sys:String x:Key="Setup.GameManage.Community.FilenameFormat">File name format</sys:String>
<sys:String x:Key="Setup.GameManage.Community.FilenameFormat.Style0">【Create】create-1.21.1-6.0.4</sys:String>
<sys:String x:Key="Setup.GameManage.Community.FilenameFormat.Style1">[Create] create-1.21.1-6.0.4</sys:String>
<sys:String x:Key="Setup.GameManage.Community.FilenameFormat.Style2">Create-create-1.21.1-6.0.4</sys:String>
<sys:String x:Key="Setup.GameManage.Community.FilenameFormat.Style3">create-1.21.1-6.0.4-Create</sys:String>
<sys:String x:Key="Setup.GameManage.Community.FilenameFormat.Style4">create-1.21.1-6.0.4</sys:String>
<sys:String x:Key="Setup.GameManage.Community.FilenameFormat.ToolTip">Default file name format when downloading community resources</sys:String>
<sys:String x:Key="Setup.GameManage.Community.HideQuilt">Hide Quilt loader</sys:String>
<sys:String x:Key="Setup.GameManage.Community.HideQuilt.ToolTip">Hide Quilt from the community resource loader display.</sys:String>
<sys:String x:Key="Setup.GameManage.Community.ModManageStyle">Mod management style</sys:String>
<sys:String x:Key="Setup.GameManage.Community.ModManageStyle.TitleFilename">Title shows file name, details show translated name</sys:String>
<sys:String x:Key="Setup.GameManage.Community.ModManageStyle.TitleTranslation">Title shows translated name, details show file name</sys:String>
<sys:String x:Key="Setup.GameManage.Community.ModManageStyle.ToolTip">Display style of mod items on the Mod management page</sys:String>
<sys:String x:Key="Setup.GameManage.Community.QuickDownload">Quick download behavior</sys:String>
<sys:String x:Key="Setup.GameManage.Community.QuickDownload.AlwaysAsk">Always ask</sys:String>
<sys:String x:Key="Setup.GameManage.Community.QuickDownload.AskInstance">Ask which instance to use</sys:String>
<sys:String x:Key="Setup.GameManage.Community.QuickDownload.AskPath">Ask where to save</sys:String>
<sys:String x:Key="Setup.GameManage.Community.QuickDownload.CurrentInstance">Download to the current instance</sys:String>
<sys:String x:Key="Setup.GameManage.Community.QuickDownload.ToolTip">Choose what happens when you click the quick download button on a resource card</sys:String>
<sys:String x:Key="Setup.GameManage.Community.Source">Download source</sys:String>
<sys:String x:Key="Setup.GameManage.Community.Source.PreferOfficialWithFallback">Use mirrors only when the official source is slow</sys:String>
<sys:String x:Key="Setup.GameManage.Community.Title">Community resources</sys:String>
<!-- Setup.GameManage.Download -->
<sys:String x:Key="Setup.GameManage.Download.AutoSelectInstance">Auto-select the instance after installation</sys:String>
<sys:String x:Key="Setup.GameManage.Download.FixAuthlib">Upgrade Authlib for some versions</sys:String>
<sys:String x:Key="Setup.GameManage.Download.FixAuthlib.ToolTip">Some Minecraft versions, such as 1.16.5, have a bug that may cause multiplayer to be disabled when using offline login.&#xA;PCL CE can try to upgrade the Authlib support library for these versions to fix the issue.&#xA;This setting only applies to newly installed instances.</sys:String>
<sys:String x:Key="Setup.GameManage.Download.InstallBehavior">Install behavior</sys:String>
<sys:String x:Key="Setup.GameManage.Download.SpeedLimit">Speed limit</sys:String>
<sys:String x:Key="Setup.GameManage.Download.SpeedLimit.ToolTip">Set a download speed cap to prevent other programs that depend on the internet from freezing during downloads.</sys:String>
<sys:String x:Key="Setup.GameManage.Download.TargetFolder">Target folder</sys:String>
<sys:String x:Key="Setup.GameManage.Download.TargetFolder.Hint">Go to Launch → Selection → Folder list to change the download target folder.&#xA;Right-click a folder or game instance to open the corresponding folder.</sys:String>
<sys:String x:Key="Setup.GameManage.Download.Threads">Max threads</sys:String>
<sys:String x:Key="Setup.GameManage.Download.Threads.TooManyWarning.Confirm">I see</sys:String>
<sys:String x:Key="Setup.GameManage.Download.Threads.TooManyWarning.Message">Setting too many download threads may cause serious lag during downloads.&#xA;64 threads are generally enough for most download needs. Unless you know what you are doing, it is not recommended to set a higher value!</sys:String>
<sys:String x:Key="Setup.GameManage.Download.Threads.ToolTip">More threads can speed up rate-limited downloads, but too many may cause serious lag during downloads.&#xA;In general, 64 threads are enough for most download needs.</sys:String>
<sys:String x:Key="Setup.GameManage.Download.Unlimited">Unlimited</sys:String>
<!-- Setup.Java -->
<sys:String x:Key="Setup.Java.Add">Add</sys:String>
<sys:String x:Key="Setup.Java.AddFailed">Failed to add Java to the list.</sys:String>
<sys:String x:Key="Setup.Java.Added">Java added!</sys:String>
<sys:String x:Key="Setup.Java.AlreadyExists">This Java already exists, so it does not need to be added again...</sys:String>
<sys:String x:Key="Setup.Java.AutoSelect.Info">Java auto mode: automatically selects the appropriate Java based on game requirements.</sys:String>
<sys:String x:Key="Setup.Java.AutoSelect.Title">Auto select</sys:String>
<sys:String x:Key="Setup.Java.DeselectBeforeDisable">Please deselect this Java as default before disabling it.</sys:String>
<sys:String x:Key="Setup.Java.Detail.ToolTip">Details</sys:String>
<sys:String x:Key="Setup.Java.Disable">Disable this Java</sys:String>
<sys:String x:Key="Setup.Java.Enable">Enable this Java</sys:String>
<sys:String x:Key="Setup.Java.EnableBeforeSelect">Please enable this Java before selecting it as default.</sys:String>
<sys:String x:Key="Setup.Java.EnableFailed">Failed to change Java enabled state</sys:String>
<sys:String x:Key="Setup.Java.Info.Format">Type: {0}&#xA;Version: {1}&#xA;Architecture: {2} ({3})&#xA;Vendor: {4}&#xA;Location: {5}</sys:String>
<sys:String x:Key="Setup.Java.Info.Title">Java information</sys:String>
<sys:String x:Key="Setup.Java.Loading">Loading Java list...</sys:String>
<sys:String x:Key="Setup.Java.SelectFile.Filter">Java program (java.exe)|java.exe</sys:String>
<sys:String x:Key="Setup.Java.SelectFile.Title">Select Java program</sys:String>
<sys:String x:Key="Setup.Java.Unavailable">This Java is unavailable. Please refresh the list.</sys:String>
<!-- Setup.Language -->
<sys:String x:Key="Setup.Language.CardTitle">Language</sys:String>
<sys:String x:Key="Setup.Language.Changed">Language settings updated</sys:String>
<sys:String x:Key="Setup.Language.FormatCulture">Regional format</sys:String>
<sys:String x:Key="Setup.Language.FormatCulture.Auto">Follow system regional format</sys:String>
<sys:String x:Key="Setup.Language.FormatCulture.FollowLanguage">Follow UI language</sys:String>
<sys:String x:Key="Setup.Language.Banner.TitlePrefix">Let</sys:String>
<sys:String x:Key="Setup.Language.Banner.TitleSuffix">Go Internationalized.</sys:String>
<sys:String x:Key="Setup.Language.Banner.DescriptionLine1">With the support of the community, PCL CE is entering a new chapter in its journey toward internationalization.</sys:String>
<sys:String x:Key="Setup.Language.Banner.DescriptionLine2">You can help shape that journey by submitting pull requests to our GitHub repository and making PCL CE more accessible to users around the world.</sys:String>
<sys:String x:Key="Setup.Language.Banner.DescriptionLine3">Thank you to everyone who contributes translations, reviews, and improvements—your support makes PCL CE better for everyone.</sys:String>
<sys:String x:Key="Setup.Language.Banner.SubmitPullRequest">Submit pull request</sys:String>
<sys:String x:Key="Setup.Language.Banner.ViewDeveloperDocs">View developer docs</sys:String>
<sys:String x:Key="Setup.Language.Reset.Success">Language page settings initialized!</sys:String>
<sys:String x:Key="Setup.Language.Title">Language</sys:String>
<sys:String x:Key="Setup.Language.UiLanguage">Display language</sys:String>
<sys:String x:Key="Setup.Language.UiLanguage.Auto">Follow system ({0})</sys:String>
<!-- Setup.Launch -->
<sys:String x:Key="Setup.Launch.Error.ConfigReset">Launch settings are abnormal and have been automatically reset</sys:String>
<sys:String x:Key="Setup.Launch.Error.InitFailed">Failed to initialize launch settings</sys:String>
<sys:String x:Key="Setup.Launch.Error.LoadFailed">Failed to reload launch settings</sys:String>
<sys:String x:Key="Setup.Launch.Initialized">Launch settings have been initialized!</sys:String>
<sys:String x:Key="Setup.Launch.SwitchToInstanceSetup">Instance settings</sys:String>
<!-- Setup.Launch.Advanced -->
<sys:String x:Key="Setup.Launch.Advanced.DisableCrashAnalysis">Disable automatic crash analysis</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableCrashAnalysis.ToolTip">When enabled, the crash analysis page will no longer automatically appear when the game crashes.</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableJlw">Disable Java Launch Wrapper</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableJlw.Arm64Notice">Do not wrap with Java Wrapper when launching the game.&#xA;Java Wrapper has been forcibly disabled because the system is ARM64 architecture.</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableJlw.Arm64ToolTip">Do not use Java Wrapper when launching the game.&#xA;Java Wrapper is disabled because this system uses ARM64 architecture.</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableJlw.ToolTip">PCL CE uses Java Launch Wrapper to fix issues where older Java versions cannot launch from paths containing Chinese characters.&#xA;If it is blocked by anti-cheat or causes other issues, you can disable it.</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableLF">Disable LegacyFix</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableLF.ToolTip">PCL CE uses LegacyFix when launching version 1.5.x and below to improve legacy version gameplay experience and fix compatibility issues.</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.LockMemory">Lock memory allocation (-Xms = -Xmx)</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.LockMemory.ToolTip">When enabled, the launcher additionally appends -Xms equal to the maximum heap (-Xmx), fixing the heap size. This implicitly disables heap memory uncommit, reducing latency jitter caused by the OS unmapping and remapping memory pages, and helps ZGC's collection prediction. The trade-off is that the game holds close to the maximum allocated memory for its entire runtime.</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableLwjglUnsafeAgent">Disable LWJGL Unsafe Agent</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableLwjglUnsafeAgent.ToolTip">Minecraft 26.1 uses LWJGL 3.4.1, which uses the FFM API to access native memory on JDK 25+. Due to JDK optimization limitations, this may cause severe performance issues in some cases.&#xA;PCL CE uses LWJGL Unsafe Agent to fix this issue and avoid possible performance problems.&#xA;There is generally no need to disable this option, but if you encounter additional issues, you can try temporarily disabling it for troubleshooting.&#xA;&#xA;This feature only takes effect when the LWJGL version is 3.4.1.</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.GameTail">Game arguments tail</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.GameTail.ToolTip">The content in this text box will be directly appended to the end of the launch arguments.&#xA;For example, entering --demo will start the game in demo mode.&#xA;String replacement tokens from the Minecraft instance JSON, such as ${library_directory}, are supported.</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.GpuPreference">Require Java to use the high-performance GPU</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.GpuPreference.ToolTip">Automatically set Java to use the high-performance GPU in Windows Settings → System → Display → Graphics.&#xA;If the computer has both integrated and dedicated graphics, this ensures that Minecraft uses the dedicated GPU for rendering, greatly improving performance.</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.JvmHead">JVM arguments head</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.JvmHead.Restore">Restore</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.JvmHead.ToolTip">Extra JVM arguments used when launching Minecraft. Do not change unless you are absolutely sure.&#xA;Supports string replacement tokens from Minecraft instance JSON, e.g. ${library_directory}.</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.MoreInInstanceSetup">You can find more advanced launch options in instance settings.</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.PreLaunchCommand">Pre-launch command</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.PreLaunchCommand.ToolTip">Execute a specific command or program before Minecraft launches. The syntax is the same as Windows Command Prompt.&#xA;&#xA;The following replacement tokens can be used for relative paths (paths all end with \):&#xA; · {path}: The folder containing PCL CE's exe file&#xA; · {minecraft}: The .minecraft folder&#xA; · {verpath}: Instance folder (.minecraft\versions\instance_name\)&#xA; · {verindie}: Same as the instance folder when instance isolation is enabled; otherwise same as the .minecraft folder&#xA; · {java}: The Java folder used by the game&#xA;&#xA;The following replacement tokens are also supported:&#xA; · {user}: Username&#xA; · {login}: Login method&#xA; · {uuid}: Player UUID&#xA; · {name}: Instance name&#xA; · {date}, {time}: System time&#xA; · {version}: Vanilla game version&#xA;&#xA;Examples:&#xA; · "{verpath}test.exe": Run test.exe in the instance folder&#xA; · "{java}java.exe" -jar "{verpath}test.jar": Use Java to run test.jar in the instance folder&#xA; · notepad "{verindie}options.txt": Open the instance settings file with Notepad&#xA;&#xA;It is recommended to wrap paths in double quotes to avoid launch failures caused by spaces in the path.&#xA;When the command is executed, the working directory is the current .minecraft folder.</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.PreLaunchCommand.Wait">Wait for command completion before continuing launch</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.Renderer.DirectX12">DirectX 12 (d3d12)</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.Renderer.GameDefault">Game default</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.Renderer.Label">Renderer</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.Renderer.Software">Software renderer (llvmpipe)</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.Renderer.Vulkan">Vulkan (zink)</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.Renderer.Warning.Confirm">I know what I am doing</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.Renderer.Warning.Message">Changing this setting will seriously affect game stability and performance. If you do not know what you are doing, do not change this option!&#xA;Are you sure you want to continue?</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.Title">Advanced launch options</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.UseJavaExe">Use java.exe instead of javaw.exe</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.UseJavaExe.ToolTip">If the game window does not appear for a long time, try enabling this option. PCL CE will use java.exe to run the game, giving the game process standard input/output streams to avoid possible issues when logs are printed.&#xA;&#xA;Depending on the behavior of different Java distributions, java.exe may not support features that depend on operating system support, such as HDR colors. Therefore, do not enable this option if everything works normally.</sys:String>
<!-- Setup.Launch.Memory -->
<sys:String x:Key="Setup.Launch.Memory.Auto">Auto</sys:String>
<sys:String x:Key="Setup.Launch.Memory.Auto.ToolTip">Dynamically adjust the memory allocated to the game based on the installed mods and available system memory.</sys:String>
<sys:String x:Key="Setup.Launch.Memory.AvailableSuffix">Available: {0} GiB</sys:String>
<sys:String x:Key="Setup.Launch.Memory.GameAllocation">Game allocation</sys:String>
<sys:String x:Key="Setup.Launch.Memory.Title">Game memory</sys:String>
<sys:String x:Key="Setup.Launch.Memory.UsedTotal">Used / installed memory</sys:String>
<sys:String x:Key="Setup.Launch.Memory.Warning.Java32Bit">32-bit Java can only allocate up to 1 GiB of memory. It is strongly recommended to use 64-bit Java!</sys:String>
<sys:String x:Key="Setup.Launch.Memory.Warning.TooMuch">You have allocated too much memory to the game, which may cause crashes. It is recommended to use Auto first!</sys:String>
<!-- Setup.Launch.Options -->
<sys:String x:Key="Setup.Launch.Options.CustomInfo.Label">Custom text</sys:String>
<sys:String x:Key="Setup.Launch.Options.CustomInfo.ToolTip">This text will be displayed in the title screen and the F3 debug screen.</sys:String>
<sys:String x:Key="Setup.Launch.Options.IpStack.Ipv4">IPv4 preferred</sys:String>
<sys:String x:Key="Setup.Launch.Options.IpStack.Ipv6">IPv6 preferred</sys:String>
<sys:String x:Key="Setup.Launch.Options.IpStack.JavaDefault">Java default</sys:String>
<sys:String x:Key="Setup.Launch.Options.IpStack.Label">IP protocol preference</sys:String>
<sys:String x:Key="Setup.Launch.Options.IpStack.ToolTip">Set the Minecraft IP protocol preference through JVM arguments.&#xA;In general, IPv4 preferred is recommended to ensure that the PCL CE lobby works properly.&#xA;If you need manual IPv6 networking, you can choose another option.&#xA;&#xA;IPv4 preferred: -Djava.net.preferIPv4Stack=true&#xA;Default: no extra arguments&#xA;IPv6 preferred: -Djava.net.preferIPv6Stack=true</sys:String>
<sys:String x:Key="Setup.Launch.Options.MsAuth.DeviceCode">Device Code Flow</sys:String>
<sys:String x:Key="Setup.Launch.Options.MsAuth.Label">Microsoft login method</sys:String>
<sys:String x:Key="Setup.Launch.Options.MsAuth.ToolTip">The Web Account Manager has a simpler Microsoft login flow, while the Device Code Flow allows you to authenticate on any device with a web browser.&#xA;If you encounter issues with the Web Account Manager, try using the Device Code Flow.&#xA;If you are temporarily logging in on a public computer, Device Code Flow is recommended.&#xA;&#xA;Due to issues with some dependencies, this option is temporarily forced to Device Code Flow.</sys:String>
<sys:String x:Key="Setup.Launch.Options.MsAuth.WebManager">Web Account Manager (temporarily forced to Device Code Flow)</sys:String>
<sys:String x:Key="Setup.Launch.Options.Priority.AboveNormal">High (prioritizes game performance, but may cause other programs to lag)</sys:String>
<sys:String x:Key="Setup.Launch.Options.Priority.BelowNormal">Low (prioritizes other programs, but may cause the game to lag; suitable for running in the background)</sys:String>
<sys:String x:Key="Setup.Launch.Options.Priority.High">Very high (use with caution, may severely impact other programs)</sys:String>
<sys:String x:Key="Setup.Launch.Options.Priority.Label">Process priority</sys:String>
<sys:String x:Key="Setup.Launch.Options.Priority.Normal">Normal (balanced)</sys:String>
<sys:String x:Key="Setup.Launch.Options.Priority.RealTime">Realtime (runs the game at the highest priority; use with caution, may severely impact system performance)</sys:String>
<sys:String x:Key="Setup.Launch.Options.Title">Launch options</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowSize.Custom">Custom size</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowSize.CustomHeightBug.ToolTip">Some versions such as Forge 1.12.2 have a bug that causes the height setting to not take effect</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowSize.Fullscreen">Fullscreen</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowSize.Label">Window size</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowSize.Maximized">Maximized</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowSize.ModernUiWarning">Note: If your instance has the Modern UI mod installed, this setting will not take effect</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowSize.SameAsLauncher">Same as launcher size</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowTitle.Label">Window title</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowTitle.SampleFormat">{name} | Player: {user} | Logged in as {login}</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowTitle.ToolTip">Customize the game window title. Leave it blank to keep the default.&#xA;The following alternate tags are supported:&#xA; · {user}: Username&#xA; · {login}: Login method&#xA; · {name}: Instance name&#xA; · {date}, {time}: System time&#xA; · {version}: Vanilla game version&#xA;Note: If your instance has the Modern UI mod installed, this setting will not take effect.</sys:String>
<!-- Setup.Launch.Options.InstanceIsolation -->
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.All">Isolate all instances</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.All.ToolTip">Saves, mods, resource packs, and other files are not shared between instances.&#xA;Saves will not be shared between Vanilla instances either, so this is generally not recommended.</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.DefaultPolicyHint">The default policy only applies to newly installed instances.&#xA;Existing instances' isolation policy needs to be adjusted in their settings.</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.Label">Default instance isolation</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.ModdedAndNonRelease">Isolate instances with mod support and non-releases</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.ModdedOnly">Isolate instances with mod support</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.ModdedOnly.ToolTip">Isolate instances with mod support like Forge and Fabric to avoid mod conflicts, while other instances like Vanilla are not isolated.</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.NonRelease">Isolate non-releases</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.NonRelease.ToolTip">Isolate snapshots, pre-releases, old versions, and April Fools' versions from other instances.</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.None.ToolTip">All instances use the same profile. Saves, mods, resource packs, and other files are shared.&#xA;If multiple instances with mods installed are present, this may cause mod conflicts.</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.ToolTip">When installing a new instance, the isolation option for the new instance will be automatically set accordingly.&#xA;To adjust the isolation policy for an existing instance, please go to instance settings.</sys:String>
<!-- Setup.Launch.Options.Visibility -->
<sys:String x:Key="Setup.Launch.Options.Visibility.CloseImmediately">Close immediately after game starts</sys:String>
<sys:String x:Key="Setup.Launch.Options.Visibility.CloseImmediately.Warning.Continue">Continue</sys:String>
<sys:String x:Key="Setup.Launch.Options.Visibility.CloseImmediately.Warning.Message">If the launcher is closed immediately after the game starts, features such as crash detection and changing the game title will be unavailable.&#xA;To keep these features, you can choose to hide the launcher after the game starts and close it automatically after the game exits.</sys:String>
<sys:String x:Key="Setup.Launch.Options.Visibility.CloseImmediately.Warning.Title">Reminder</sys:String>
<sys:String x:Key="Setup.Launch.Options.Visibility.HideAndCloseOnExit">Hide after game starts, close automatically after game exits</sys:String>
<sys:String x:Key="Setup.Launch.Options.Visibility.HideAndReopenOnExit">Hide after game starts, reopen after game exits</sys:String>
<sys:String x:Key="Setup.Launch.Options.Visibility.KeepUnchanged">Keep unchanged after game starts</sys:String>
<sys:String x:Key="Setup.Launch.Options.Visibility.Label">Launcher visibility</sys:String>
<sys:String x:Key="Setup.Launch.Options.Visibility.Minimize">Minimize after game starts</sys:String>
<!-- Setup.Left -->
<sys:String x:Key="Setup.Left.Category.About">About</sys:String>
<sys:String x:Key="Setup.Left.Category.Game">Game</sys:String>
<sys:String x:Key="Setup.Left.Category.Launcher">Launcher</sys:String>
<sys:String x:Key="Setup.Left.Category.Tools">Tools</sys:String>
<sys:String x:Key="Setup.Left.Feedback.Message">Please check the feedback list for similar issues before submitting new feedback to avoid duplicate submissions.</sys:String>
<sys:String x:Key="Setup.Left.Feedback.SubmitNew">New feedback</sys:String>
<sys:String x:Key="Setup.Left.Feedback.Title">Feedback</sys:String>
<sys:String x:Key="Setup.Left.Feedback.ViewList">Feedback list</sys:String>
<sys:String x:Key="Setup.Left.Refreshing">Refreshing...</sys:String>
<sys:String x:Key="Setup.Left.Reset.GameLink.Message">Do you want to initialize all settings on the Tools Multiplayer page? This action cannot be undone.</sys:String>
<sys:String x:Key="Setup.Left.Reset.GameManage.Message">Do you want to initialize all settings on the Game Management page? This action cannot be undone.</sys:String>
<sys:String x:Key="Setup.Left.Reset.Language.Message">Do you want to initialize all settings on the Launcher Language page? This action cannot be undone.</sys:String>
<sys:String x:Key="Setup.Left.Reset.Launch.Message">Do you want to initialize all settings on the Game Launch page? This action cannot be undone.</sys:String>
<sys:String x:Key="Setup.Left.Reset.Misc.Message">Do you want to initialize all settings on the Launcher Miscellaneous page? This action cannot be undone.</sys:String>
<sys:String x:Key="Setup.Left.Reset.Title">Initialization confirmation</sys:String>
<sys:String x:Key="Setup.Left.Reset.Ui.Message">Do you want to initialize all settings on the Launcher Personalization page? This action cannot be undone.&#xA;(Background images, music, homepage files, and other external files will not be deleted.)</sys:String>
<!-- Setup.Left.Item -->
<sys:String x:Key="Setup.Left.Item.About">About</sys:String>
<sys:String x:Key="Setup.Left.Item.Feedback">Feedback</sys:String>
<sys:String x:Key="Setup.Left.Item.GameLink">Multiplayer</sys:String>
<sys:String x:Key="Setup.Left.Item.GameManage">Management</sys:String>
<sys:String x:Key="Setup.Left.Item.Java">Java</sys:String>
<sys:String x:Key="Setup.Left.Item.Launch">Launch</sys:String>
<sys:String x:Key="Setup.Left.Item.Log">Logs</sys:String>
<sys:String x:Key="Setup.Left.Item.Misc">Miscellaneous</sys:String>
<sys:String x:Key="Setup.Left.Item.Ui">Personalization</sys:String>
<sys:String x:Key="Setup.Left.Item.Update">Updates</sys:String>
<!-- Setup.Log -->
<sys:String x:Key="Setup.Log.AllLogs">All logs</sys:String>
<sys:String x:Key="Setup.Log.Clear.Confirm.Message">Do you want to delete all historical logs?</sys:String>
<sys:String x:Key="Setup.Log.Clear.Confirm.Title">Clear historical logs</sys:String>
<sys:String x:Key="Setup.Log.Clear.Success">Log files cleaned successfully!</sys:String>
<sys:String x:Key="Setup.Log.ClearHistory">Clear historical logs</sys:String>
<sys:String x:Key="Setup.Log.CurrentSuffix">{0} (current)</sys:String>
<sys:String x:Key="Setup.Log.Export">Export log</sys:String>
<sys:String x:Key="Setup.Log.ExportAll">Export all logs</sys:String>
<sys:String x:Key="Setup.Log.ExportFailed">Failed to save log</sys:String>
<sys:String x:Key="Setup.Log.ExportFilter">PCL CE log archive|*.zip</sys:String>
<sys:String x:Key="Setup.Log.ExportSaveTitle">Export log file</sys:String>
<sys:String x:Key="Setup.Log.ExportSuccess">Log saved successfully!</sys:String>
<sys:String x:Key="Setup.Log.OpenDirectory">Open log folder</sys:String>
<sys:String x:Key="Setup.Log.Operations">Log operations</sys:String>
<sys:String x:Key="Setup.Log.TempStored">Temporarily stored logs ({0})</sys:String>
<sys:String x:Key="Setup.Log.TempUnoutput">Temporarily stored logs that have not been output</sys:String>
<!-- Setup.Misc -->
<sys:String x:Key="Setup.Misc.Error.InitFailed">Failed to initialize miscellaneous settings</sys:String>
<sys:String x:Key="Setup.Misc.HardwareAcceleration.RestartNotice">This change will take effect after restarting PCL CE.</sys:String>
<sys:String x:Key="Setup.Misc.Initialized">Miscellaneous settings have been initialized!</sys:String>
<!-- Setup.Misc.Debug -->
<sys:String x:Key="Setup.Misc.Debug.AnimationSpeed">Animation speed</sys:String>
<sys:String x:Key="Setup.Misc.Debug.Delay">Add delay</sys:String>
<sys:String x:Key="Setup.Misc.Debug.Delay.ToolTip">Add random delays at various stages, slowing down loading to test whether certain features work correctly.&#xA;This will seriously affect launcher performance. Not recommended unless specifically needed.</sys:String>
<sys:String x:Key="Setup.Misc.Debug.Mode">Debug mode</sys:String>
<sys:String x:Key="Setup.Misc.Debug.Mode.Hint">Some debug information will switch display after refresh or launcher restart!</sys:String>
<sys:String x:Key="Setup.Misc.Debug.Mode.ToolTip">Show debug information and more error details.&#xA;This may slightly decrease launcher performance. Not recommended unless specifically needed.</sys:String>
<sys:String x:Key="Setup.Misc.Debug.SkipCopy">Do not copy files from other folders while downloading</sys:String>
<sys:String x:Key="Setup.Misc.Debug.SkipCopy.ToolTip">When downloading, do not copy identical files that already exist in other Minecraft folders; re-download them instead.&#xA;Only recommended for testing download speed.</sys:String>
<sys:String x:Key="Setup.Misc.Debug.Title">Debug options</sys:String>
<!-- Setup.Misc.Network -->
<sys:String x:Key="Setup.Misc.Network.DoH">Use DoH for address resolution</sys:String>
<sys:String x:Key="Setup.Misc.Network.DoH.ToolTip">Using DoH for address resolution can provide better IP results and improve the launcher's network access. If you have deployed a DoH resolution service, you can disable this feature.</sys:String>
<sys:String x:Key="Setup.Misc.Network.HttpProxy">HTTP proxy</sys:String>
<sys:String x:Key="Setup.Misc.Network.Title">Network</sys:String>
<!-- Setup.Misc.Network.Proxy -->
<sys:String x:Key="Setup.Misc.Network.Proxy.Apply">Apply proxy information</sys:String>
<sys:String x:Key="Setup.Misc.Network.Proxy.Custom">Custom proxy</sys:String>
<sys:String x:Key="Setup.Misc.Network.Proxy.InvalidWarning">Incorrect proxy server addresses will cause network connection failures. Network issues caused by proxy usage will not be handled.&#xA;Do not enter untrusted proxy server addresses, as this may lead to account information leakage or even account theft.</sys:String>
<sys:String x:Key="Setup.Misc.Network.Proxy.None">No proxy</sys:String>
<sys:String x:Key="Setup.Misc.Network.Proxy.Optional">If needed</sys:String>
<sys:String x:Key="Setup.Misc.Network.Proxy.Password">Password</sys:String>
<sys:String x:Key="Setup.Misc.Network.Proxy.System">Use system proxy</sys:String>
<sys:String x:Key="Setup.Misc.Network.Proxy.UntrustedWarning">Do not enter untrusted proxy addresses</sys:String>
<sys:String x:Key="Setup.Misc.Network.Proxy.Username">Account</sys:String>
<!-- Setup.Misc.System -->
<sys:String x:Key="Setup.Misc.System.Announcement">Launcher announcements</sys:String>
<sys:String x:Key="Setup.Misc.System.Announcement.All">Show all announcements</sys:String>
<sys:String x:Key="Setup.Misc.System.Announcement.All.ToolTip">Show all announcements, including launcher feature surveys, events, and more.</sys:String>
<sys:String x:Key="Setup.Misc.System.Announcement.Disabled">Disable all announcements</sys:String>
<sys:String x:Key="Setup.Misc.System.Announcement.Disabled.Warning.Confirm">I know what I am doing</sys:String>
<sys:String x:Key="Setup.Misc.System.Announcement.Disabled.Warning.Message">If you choose this option, you will not receive relevant notifications even when serious issues occur in the future.&#xA;For example, if a critical bug is found in a certain game version, you may miss the notification and face unpredictable consequences.&#xA;&#xA;In general, choosing "Show announcements only when important" should keep you mostly undisturbed.&#xA;Unless you are creating a server modpack or frequently update the launcher manually, this option is strongly discouraged!</sys:String>
<sys:String x:Key="Setup.Misc.System.Announcement.ImportantOnly">Show announcements only when important</sys:String>
<sys:String x:Key="Setup.Misc.System.DisableHardwareAcceleration">Disable hardware acceleration</sys:String>
<sys:String x:Key="Setup.Misc.System.ExportSettings">Export settings</sys:String>
<sys:String x:Key="Setup.Misc.System.ExportSettings.Filter">PCL CE config file (*.json)|*.json</sys:String>
<sys:String x:Key="Setup.Misc.System.ExportSettings.SaveTitle">Select save location</sys:String>
<sys:String x:Key="Setup.Misc.System.ExportSettings.Success">Settings exported successfully!</sys:String>
<sys:String x:Key="Setup.Misc.System.ImportSettings">Import settings</sys:String>
<sys:String x:Key="Setup.Misc.System.ImportSettings.SelectTitle">Select config file</sys:String>
<sys:String x:Key="Setup.Misc.System.ImportSettings.Success.Message">Settings imported successfully! Please restart PCL CE to apply them...</sys:String>
<sys:String x:Key="Setup.Misc.System.ImportSettings.Success.Restart">Restart</sys:String>
<sys:String x:Key="Setup.Misc.System.MaxFps">Maximum animation FPS</sys:String>
<sys:String x:Key="Setup.Misc.System.MaxFps.ToolTip">If you have disabled hardware acceleration, it is recommended to lower the maximum animation FPS to prevent excessive resource usage.</sys:String>
<sys:String x:Key="Setup.Misc.System.MaxLogLines">Real-time log lines</sys:String>
<sys:String x:Key="Setup.Misc.System.MaxLogLines.ToolTip">Maximum number of lines displayed in the real-time log feature. Old logs are automatically deleted when exceeded.</sys:String>
<sys:String x:Key="Setup.Misc.System.MaxLogLines.Unlimited">Unlimited</sys:String>
<sys:String x:Key="Setup.Misc.System.StopUsingPclCe">Stop using PCL CE</sys:String>
<sys:String x:Key="Setup.Misc.System.StopUsingPclCe.ContinueAndRemove">Continue and remove instance configs</sys:String>
<sys:String x:Key="Setup.Misc.System.StopUsingPclCe.Message">This will delete all PCL CE settings, cached data, saved account information, and other related data from this computer. Your acceptance of any license agreements will also be revoked. PCL CE will close automatically when the process is complete.&#xA;&#xA;The following cannot be removed automatically:&#xA;- Local data from other PCL CE installations on this computer&#xA;- Your Natayark account&#xA;&#xA;To also remove PCL CE configuration files from Minecraft instances, choose "Continue and remove instance configs". This will not affect Minecraft settings, mods, saves, or other game data.&#xA;&#xA;This action cannot be undone. Are you sure you want to continue?</sys:String>
<sys:String x:Key="Setup.Misc.System.StopUsingPclCe.Message.Final">This is the final warning! Are you sure you want to continue?&#xA;All PCL CE data on this computer will be lost forever! (A long time!)</sys:String>
<sys:String x:Key="Setup.Misc.System.StopUsingPclCe.Title">Stop using PCL CE?</sys:String>
<sys:String x:Key="Setup.Misc.System.Telemetry">Enable telemetry data collection</sys:String>
<sys:String x:Key="Setup.Misc.System.Telemetry.ToolTip">When enabled, the launcher will collect and report errors and device environment information. This helps developers fix potential issues and better plan and develop the launcher.&#xA;The following information will be collected: launcher errors, launcher version information and identifier, Windows system version and architecture, installed physical memory size, NAT and IPv6 support status, and whether original PCL, HMCL, or BakaXL have been used.&#xA;&#xA;This data is not associated with you, and we will never sell it to any third party. Not enabling this will not affect other features, but may affect developers' ability to fix potential bugs.&#xA;&#xA;This change will take effect after restarting the launcher.</sys:String>
<sys:String x:Key="Setup.Misc.System.Title">System</sys:String>
<!-- Setup.Ui -->
<sys:String x:Key="Setup.Ui.Error.BackgroundVideoUnsupported">Failed to refresh the background. The video may not be encoded as H.264 (AVC). Re-encode it as H.264 (AVC) and try again.</sys:String>
<sys:String x:Key="Setup.Ui.Error.ConfigReset">There is an exception in the personalization settings and it has been automatically reset.</sys:String>
<sys:String x:Key="Setup.Ui.Error.InitFailed">Failed to initialize the personalization settings.</sys:String>
<sys:String x:Key="Setup.Ui.Error.LoadFailed">Error while loading the personalization settings.</sys:String>
<sys:String x:Key="Setup.Ui.Error.OperationFailed">An interface settings operation failed. See the details below.</sys:String>
<sys:String x:Key="Setup.Ui.Error.TitleImageInvalidFormat">Failed to change the title bar image. The image may not use a supported format. Open it in Paint and save it again, then try again.</sys:String>
<sys:String x:Key="Setup.Ui.Error.TitleImageResizeInvalidFormat">Failed to resize the title bar image. The image may not use a supported format. Open it in Paint and save it again, then try again.</sys:String>
<sys:String x:Key="Setup.Ui.Font.Global">Global</sys:String>
<sys:String x:Key="Setup.Ui.Font.Global.ToolTip">Global default font</sys:String>
<sys:String x:Key="Setup.Ui.Font.Motd">MOTD</sys:String>
<sys:String x:Key="Setup.Ui.Font.Motd.ToolTip">Formatted code text font, used for server status info and similar content</sys:String>
<sys:String x:Key="Setup.Ui.Font.Title">Font</sys:String>
<sys:String x:Key="Setup.Ui.ImageFile.Filter">Common image files (*.png;*.jpg;*.gif;*.webp)|*.png;*.jpg;*.gif;*.webp</sys:String>
<sys:String x:Key="Setup.Ui.ImageFile.SelectTitle">Select image</sys:String>
<sys:String x:Key="Setup.Ui.Initialized">The personalization settings have been initialized.</sys:String>
<sys:String x:Key="Setup.Ui.Slider.Pixel">{0}px</sys:String>
<!-- Setup.Ui.Background -->
<sys:String x:Key="Setup.Ui.Background.Blur">Background blur</sys:String>
<sys:String x:Key="Setup.Ui.Background.Blur.ToolTip">Strong blur may cause serious FPS drops in the launcher. Please use it cautiously.&#xA;It is not recommended to enable blur when making a modpack to ensure that PCL CE can run smoothly on low-performance devices.</sys:String>
<sys:String x:Key="Setup.Ui.Background.Clear">Clear all</sys:String>
<sys:String x:Key="Setup.Ui.Background.Clear.Confirm.Message">All files in the background folder are about to be deleted. This operation is irrevocable. Are you sure?</sys:String>
<sys:String x:Key="Setup.Ui.Background.Clear.Success">The background has been cleared!</sys:String>
<sys:String x:Key="Setup.Ui.Background.Cleared">The background has been cleared!</sys:String>
<sys:String x:Key="Setup.Ui.Background.ColorOverlay">Overlay colored background</sys:String>
<sys:String x:Key="Setup.Ui.Background.ContentAdapt">Adaptive</sys:String>
<sys:String x:Key="Setup.Ui.Background.ImageLoadFailed">Failed to load the image, trying to play the file as a video: {0}</sys:String>
<sys:String x:Key="Setup.Ui.Background.NoAvailableContent">No available background detected!</sys:String>
<sys:String x:Key="Setup.Ui.Background.OpenFolder.ToolTip">Put background images and videos in this folder, and PCL CE will randomly load one each time it is opened.&#xA;For smoother playback, videos are recommended to use H.264 encoding.</sys:String>
<sys:String x:Key="Setup.Ui.Background.PauseVideo">Pause video backgrounds while an instance is running</sys:String>
<sys:String x:Key="Setup.Ui.Background.PauseVideo.ToolTip">If enabled, the background cannot be refreshed while the game is running</sys:String>
<sys:String x:Key="Setup.Ui.Background.Refresh">Refresh</sys:String>
<sys:String x:Key="Setup.Ui.Background.Refresh.Success">The background has been refreshed: {0}</sys:String>
<sys:String x:Key="Setup.Ui.Background.Refresh.ToolTip">Randomly load a background image or video from the folder.&#xA;For smoother playback, videos are recommended to use H.264 encoding.</sys:String>
<sys:String x:Key="Setup.Ui.Background.Title">Backgrounds</sys:String>
<sys:String x:Key="Setup.Ui.Background.TitleDefault">Backgrounds</sys:String>
<sys:String x:Key="Setup.Ui.Background.TitleWithCount">Backgrounds (total: {0})</sys:String>
<!-- Setup.Ui.Background.Suit -->
<sys:String x:Key="Setup.Ui.Background.Suit.BottomLeft">Bottom-left</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.BottomRight">Bottom-right</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.Center">Center</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.Fit">Fit</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.Fit.ToolTip">Stretch the content while keeping the aspect ratio until the entire window is filled.</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.Smart">Smart</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.Smart.ToolTip">Select "Tile" when the content is small, and "Fit" when it is large.</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.Stretch">Stretch</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.Stretch.ToolTip">Stretch the content to fill the entire window, which may alter the aspect ratio.</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.Tile">Tile</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.TopLeft">Top-left</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.TopRight">Top-right</sys:String>
<!-- Setup.Ui.Basic -->
<sys:String x:Key="Setup.Ui.Basic.AdvancedMaterial">Advanced material</sys:String>
<sys:String x:Key="Setup.Ui.Basic.AdvancedMaterial.ToolTip">When enabled, blur effects will be applied to some transparent areas, such as cards.&#xA;Note that this feature consumes significant GPU resources and may cause lag!</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlueHint">Blue, blue, and more blue! Want to escape from the blue dilemma? Use the official snapshot build!</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurMethod">Blur method</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurMethod.Box">Box blur</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurMethod.Box.ToolTip">Uses linear average sampling over a small area around each pixel, which is faster but may produce color blocks.&#xA;This blur may lose detail information but has better performance. It can be used on devices with poor performance.</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurMethod.Gaussian">Gaussian blur</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurMethod.Gaussian.ToolTip">Uses weighted average sampling with a Gaussian convolution algorithm over a large area around each pixel, which is slower.&#xA;This blur has higher quality and handles details more finely. Recommended for devices with better performance.</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurMethod.ToolTip">Sets the blur method.</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurRadius">Blur radius</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurRadius.ToolTip">Sets the sampling radius of the blur effect. Larger values produce stronger blur.</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurSamplingRate">Sampling rate</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurSamplingRate.ToolTip">Sets the sampling rate of the blur effect. Higher values produce finer blur.&#xA;If your device has poor performance, try lowering this value for better performance.</sys:String>
<sys:String x:Key="Setup.Ui.Basic.GetSnapshot">Get official snapshot build</sys:String>
<sys:String x:Key="Setup.Ui.Basic.LockWindow">Lock launcher size</sys:String>
<sys:String x:Key="Setup.Ui.Basic.Opacity">Opacity</sys:String>
<sys:String x:Key="Setup.Ui.Basic.Opacity2">Opacity</sys:String>
<sys:String x:Key="Setup.Ui.Basic.ShowLogo">Display the PCL CE logo while opening the launcher</sys:String>
<sys:String x:Key="Setup.Ui.Basic.ShowTriviaHint">Show "Trivia" when launching the game</sys:String>
<sys:String x:Key="Setup.Ui.Basic.Title">Basics</sys:String>
<!-- Setup.Ui.FeatureHide -->
<sys:String x:Key="Setup.Ui.FeatureHide.Description">You can hide unnecessary pages or disable specific features. Press F12 in any interface to show the hidden features temporarily.</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.MainPage">Main interface</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.SpecificFeature">Specific features</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.SubInstance">"Properties" subpages</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.SubSetup">"Settings" subpages</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.SubTools">"Tools" subpages</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.TemporaryHint">Press F12 to disable feature hiding settings temporarily. Do not forget it or you will not be able to change the settings back...</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Title">Feature hiding</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.TitleTemporarilyDisabled">Feature hiding (disabled temporarily, press F12 to re-enable)</sys:String>
<!-- Setup.Ui.FeatureHide.Item -->
<sys:String x:Key="Setup.Ui.FeatureHide.Item.About">About</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Download">Download</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Edit">Edit</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Export">Export</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.FeatureHide">Feature hiding</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.FeatureHide.ToolTip">Only this tab will be hidden. Disabled content settings are still valid.&#xA;This can avoid feature hiding being changed while keeping the Personalization page.</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Feedback">Feedback</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.GameLink">Multiplayer</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.GameManage">Management</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.InstanceSelect">Selection</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.InstanceSelect.ToolTip">Hide the selection and properties buttons below the Launch button.</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Java">Java</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Language">Language</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Launch">Launch</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Lobby">Lobby</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Log">Logs</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Misc">Miscellaneous</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Mod">Mod</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.ModUpdate">Mod updates</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.ModUpdate.ToolTip">Prohibit mod updates on the mod management page.</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.ResourcePack">Resource packs</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Save">Saves</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Schematic">Schematics</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Screenshot">Screenshots</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Server">Servers</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Settings">Settings</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Shader">Shaders</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Toolbox">Toolbox</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Tools">Tools</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Ui">Personalization</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Update">Update</sys:String>
<!-- Setup.Ui.Homepage -->
<sys:String x:Key="Setup.Ui.Homepage.Blank">Blank</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.DownloadUrl">URL</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.GetMore">Get more homepages</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.GoToMarket">Homepage Market</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.HintReferToModSetup">See ModSetup</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.LocalFile">Local</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.LocalFile.Hint">Read the homepage from Custom.xaml in the PCL folder.&#xA;You can edit this file to add text, images, frequently used websites, quick launch actions, and more to the homepage.</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.NetUpdate">Online</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.NetUpdate.Hint">Get homepage content from the specified URL. Server owners can also use it to update server announcements dynamically.&#xA;If you have created a stable online homepage, click this hint to share it. Approved submissions may be added to the presets!</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.NetWarning">Please exercise caution when using homepages provided by strangers. Malicious homepages may cause harm!</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.PresetLabel">Homepage presets</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Refresh">Refresh</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Refresh.Success">Homepage refreshed!</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Title">Homepage</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Tutorial">Tutorials</sys:String>
<!-- Setup.Ui.Homepage.Preset -->
<sys:String x:Key="Setup.Ui.Homepage.Preset">Preset</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.DailyModpack">Daily Modpack Recommendation (Author: wkea)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.EchoCave">Echo Cave (removed)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.GitHub">PCL GitHub Dashboard (Author: Deep-Dark-Forest)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.Magazine">Magazine Homepage (Author: CreeperIsASpy)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.McNews">Minecraft News (Author: Nattiden)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.McOfficialFeed">Minecraft Official Feed</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.McSkin">Minecraft Skin Recommendation (Author: wkea)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.McUpdateSummary">Minecraft Update Summary (Author: pynickle, partially AI-generated)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.NewsToday">News Today (Author: EYicheng)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.MinecraftKnowledge">Minecraft Knowledge (Author: awaEric233)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.ModpackRecommendation">Modpack Recommendation Engine (Author: CHMYGZ)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.Bangumi">Bangumi Anime (Author: wzyaeu)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.OpenBmclapi">OpenBMCLAPI Dashboard Lite (Authors: Silverteal, Mxmilu666)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.OpenMcim">OpenMCIM Dashboard (Author: SALTWOOD)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.PclAnnouncement">PCL CE Announcements</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.PclManual">PCL New Feature Manual (Author: WForst-Breeze)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.PclMarket">PCL Homepage Market (Author: MoYuan-CN)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.PclNews">PCL News Bulletin (Author: Joker2184)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.SimpleHomepage">Simple Homepage (Author: MFn233)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.Trivia">Trivia</sys:String>
<!-- Setup.Ui.Logo -->
<sys:String x:Key="Setup.Ui.Logo.ChangeImage">Change the icon</sys:String>
<sys:String x:Key="Setup.Ui.Logo.Clear.Success">The title bar image has been cleared!</sys:String>
<sys:String x:Key="Setup.Ui.Logo.ClearImage">Clear the icon</sys:String>
<sys:String x:Key="Setup.Ui.Logo.Image">Icon</sys:String>
<sys:String x:Key="Setup.Ui.Logo.LeftAlign">Align left</sys:String>
<sys:String x:Key="Setup.Ui.Logo.None">None</sys:String>
<sys:String x:Key="Setup.Ui.Logo.Text">Text</sys:String>
<sys:String x:Key="Setup.Ui.Logo.TextLabel">Text</sys:String>
<sys:String x:Key="Setup.Ui.Logo.Title">Title bar</sys:String>
<!-- Setup.Ui.Music -->
<sys:String x:Key="Setup.Ui.Music.AutoStart">Play when the launcher starts</sys:String>
<sys:String x:Key="Setup.Ui.Music.Clear">Clear all</sys:String>
<sys:String x:Key="Setup.Ui.Music.Clear.Confirm.Message">All files in the music folder are about to be deleted. This operation is irrevocable. Are you sure?</sys:String>
<sys:String x:Key="Setup.Ui.Music.Delete.Success">The music has been deleted!</sys:String>
<sys:String x:Key="Setup.Ui.Music.Deleting">Deleting music...</sys:String>
<sys:String x:Key="Setup.Ui.Music.OpenFolder.ToolTip">Put the music in this folder, and PCL CE will play all of them.</sys:String>
<sys:String x:Key="Setup.Ui.Music.Refresh">Refresh</sys:String>
<sys:String x:Key="Setup.Ui.Music.Refresh.ToolTip">Refresh the playlist and play music on the list.</sys:String>
<sys:String x:Key="Setup.Ui.Music.Shuffle">Shuffle playing</sys:String>
<sys:String x:Key="Setup.Ui.Music.Smtc">Integrate with System Media Transport Controls (SMTC)</sys:String>
<sys:String x:Key="Setup.Ui.Music.Smtc.ToolTip">When enabled, you can use the system control center and shortcuts to control PCL CE's media playback.&#xA;Can be used with software such as Lyricify Lite or FluentFlyout.</sys:String>
<sys:String x:Key="Setup.Ui.Music.StartInGame">Play while an instance is running</sys:String>
<sys:String x:Key="Setup.Ui.Music.StopInGame">Play while no instance is running</sys:String>
<sys:String x:Key="Setup.Ui.Music.Title">Musics</sys:String>
<sys:String x:Key="Setup.Ui.Music.TitleWithCount">Musics (total: {0})</sys:String>
<sys:String x:Key="Setup.Ui.Music.Volume">Volume</sys:String>
<!-- Setup.Ui.Theme -->
<sys:String x:Key="Setup.Ui.Theme.Color.CatBlue">LTCat Blue</sys:String>
<sys:String x:Key="Setup.Ui.Theme.Color.CrashBlue">Crash Blue</sys:String>
<sys:String x:Key="Setup.Ui.Theme.Color.Hmcl">HMCL</sys:String>
<sys:String x:Key="Setup.Ui.Theme.Color.SkyBlue">Sky Blue</sys:String>
<sys:String x:Key="Setup.Ui.Theme.Dark">Dark</sys:String>
<sys:String x:Key="Setup.Ui.Theme.DarkColor">Dark theme color</sys:String>
<sys:String x:Key="Setup.Ui.Theme.DarkColor.ToolTip">Default theme color in dark mode</sys:String>
<sys:String x:Key="Setup.Ui.Theme.FollowSystem">Follow system</sys:String>
<sys:String x:Key="Setup.Ui.Theme.Light">Light</sys:String>
<sys:String x:Key="Setup.Ui.Theme.LightColor">Light theme color</sys:String>
<sys:String x:Key="Setup.Ui.Theme.Title">Theme</sys:String>
<!-- Setup.Update -->
<sys:String x:Key="Setup.Update.Changelog.Empty">The developers seem to have forgotten to provide an update summary... Maybe click below to view the full changelog?</sys:String>
<sys:String x:Key="Setup.Update.Changelog.MoreInfo">More information about this update...</sys:String>
<sys:String x:Key="Setup.Update.Changelog.Placeholder">PCL CE 2.14.0 brings a refreshing new design, several Pigeon smart features, and a delightful cross-instance workflow to boost your productivity.</sys:String>
<sys:String x:Key="Setup.Update.Changelog.Title">About this update</sys:String>
<sys:String x:Key="Setup.Update.Changelog.Unavailable">No changelog available...</sys:String>
<sys:String x:Key="Setup.Update.Changelog.View">Full changelog</sys:String>
<sys:String x:Key="Setup.Update.CheckAgain">Check again</sys:String>
<sys:String x:Key="Setup.Update.CheckFailed">Failed to check for updates</sys:String>
<sys:String x:Key="Setup.Update.Checking">Checking for updates...</sys:String>
<sys:String x:Key="Setup.Update.DotNetMissing.DownloadRuntime">Download .NET 10 Runtime</sys:String>
<sys:String x:Key="Setup.Update.DotNetMissing.Message">A launcher update (version {0}) was found, but the new version requires .NET 10 to be installed on your computer.&#xA;You need to install .NET 10 before continuing the update.&#xA;&#xA;Click the button below to open the webpage, then select {1} under ".NET Desktop Runtime" to download.</sys:String>
<sys:String x:Key="Setup.Update.DotNetMissing.Title">Launcher update - missing runtime</sys:String>
<sys:String x:Key="Setup.Update.Error.NetworkFailed">Unable to retrieve latest version information. Please check your network connection.</sys:String>
<sys:String x:Key="Setup.Update.Install">Download and install</sys:String>
<sys:String x:Key="Setup.Update.Latest">Already up to date</sys:String>
<sys:String x:Key="Setup.Update.MirrorChyanCdk.Get">Get CDK</sys:String>
<sys:String x:Key="Setup.Update.MirrorChyanCdk.Label">MirrorChyan CDK</sys:String>
<sys:String x:Key="Setup.Update.MirrorChyanCdk.ToolTip">MirrorChyan (mirrorchyan.com) is a paid third-party application distribution platform that provides high-speed download sources in China.&#xA;This is an optional service. Leaving the CDK blank does not affect normal use or updates.&#xA;If you have a MirrorChyan CDK, you can provide it to PCL CE, and the launcher will prioritize its high-speed download source for software updates. This can also help reduce pressure on the community update server...</sys:String>
<sys:String x:Key="Setup.Update.OtherOptions.Placeholder">AquaCL 3 is the first major update of PCL CE. It brings a refreshing new design, uses the latest development technologies, completely rebuilds the core experience, and adds support for macOS, Linux, and other platforms.</sys:String>
<sys:String x:Key="Setup.Update.OtherOptions.Title">Other update options - upgrade to AquaCL</sys:String>
<sys:String x:Key="Setup.Update.RestartInstall">Restart to install</sys:String>
<!-- Setup.Update.Auto -->
<sys:String x:Key="Setup.Update.Auto.Disabled">Do not automatically check for updates (not recommended)</sys:String>
<sys:String x:Key="Setup.Update.Auto.Disabled.ToolTip">Disable automatic update checking. This may prevent you from receiving updates in a timely manner.</sys:String>
<sys:String x:Key="Setup.Update.Auto.DownloadAndInstall">Automatically download and install updates</sys:String>
<sys:String x:Key="Setup.Update.Auto.DownloadAndInstall.ToolTip">The launcher will automatically check for and download available updates, and install them after closing.</sys:String>
<sys:String x:Key="Setup.Update.Auto.DownloadAndNotify">Automatically download and prompt for update</sys:String>
<sys:String x:Key="Setup.Update.Auto.DownloadAndNotify.ToolTip">The launcher will automatically check for and download available updates, then prompt you to install.</sys:String>
<sys:String x:Key="Setup.Update.Auto.NotifyOnly">Prompt for update</sys:String>
<sys:String x:Key="Setup.Update.Auto.NotifyOnly.ToolTip">The launcher will automatically check for available updates, then prompt you to download and install.</sys:String>
<sys:String x:Key="Setup.Update.Auto.Title">Auto update settings</sys:String>
<!-- Setup.Update.Channel -->
<sys:String x:Key="Setup.Update.Channel.Beta">Beta</sys:String>
<sys:String x:Key="Setup.Update.Channel.Beta.ToolTip">Potentially unstable beta versions for early feature testing. Suitable for users who want to help test and have some technical ability.</sys:String>
<sys:String x:Key="Setup.Update.Channel.Beta.Warning.Message">You are switching the launcher update channel to Beta.&#xA;Beta versions provide previews of upcoming updates, but may include insufficiently tested features and have reduced stability.&#xA;&#xA;After upgrading to Beta, you will need to wait for the next Release version or manually re-download the launcher to switch back to Release.&#xA;This option is only recommended for users with some basic knowledge and ability. If you are creating a modpack, please use the Release version!</sys:String>
<sys:String x:Key="Setup.Update.Channel.Common.Warning.Confirm">I understand</sys:String>
<sys:String x:Key="Setup.Update.Channel.Common.Warning.Title">Before continuing...</sys:String>
<sys:String x:Key="Setup.Update.Channel.Release">Release</sys:String>
<sys:String x:Key="Setup.Update.Channel.Release.ToolTip">A relatively stable version that only receives bug fixes and some built-in resource updates. Suitable for most users.</sys:String>
<sys:String x:Key="Setup.Update.Channel.Title">Update channel</sys:String>
<!-- Setup.Update.Channel.Dev -->
<sys:String x:Key="Setup.Update.Channel.Dev">Dev</sys:String>
<sys:String x:Key="Setup.Update.Channel.Dev.FinalConfirm.ExpectedInput">I confirm switching to this branch and am aware of the risks</sys:String>
<sys:String x:Key="Setup.Update.Channel.Dev.FinalConfirm.Message">Are you sure you want to switch to the Dev channel?&#xA;Dev versions may have serious issues or even fail to launch!&#xA;&#xA;After upgrading to Dev, you will not be able to switch back to any other update channel without manually re-downloading the launcher.&#xA;&#xA;This option is only recommended for advanced users. If you are creating a modpack, please use the Release version!&#xA;Please enter "{0}" to confirm.</sys:String>
<sys:String x:Key="Setup.Update.Channel.Dev.FinalConfirm.Submit">Submit</sys:String>
<sys:String x:Key="Setup.Update.Channel.Dev.FinalConfirm.Title">Final confirmation</sys:String>
<sys:String x:Key="Setup.Update.Channel.Dev.FinalConfirm.WrongInput">The content you entered is incorrect...</sys:String>
<sys:String x:Key="Setup.Update.Channel.Dev.ToolTip">Potentially highly unstable development versions built directly from the latest source code. Suitable for highly technical users.&#xA;Good internet connectivity may be required to download updates from this channel.</sys:String>
<sys:String x:Key="Setup.Update.Channel.Dev.Warning.Message">You are switching the launcher update channel to Dev.&#xA;This channel provides early access to development builds from the latest code, but may be extremely unstable or even fail to launch.&#xA;&#xA;After upgrading to Dev, you can only manually re-download the launcher to switch back to Release or Beta.&#xA;This option is only recommended for advanced users. If you are creating a modpack, please use the Release version!</sys:String>
<!-- Speed -->
<sys:String x:Key="Speed.Error.ClickToCopy">Click to copy error details</sys:String>
<sys:String x:Key="Speed.Error.Copied">Error details copied!</sys:String>
<sys:String x:Key="Speed.Error.OperationFailed">A task manager operation failed. See the details below.</sys:String>
<sys:String x:Key="Speed.Progress.RemainingFiles">Remaining files</sys:String>
<sys:String x:Key="Speed.Progress.RemainingThreads">Remaining threads</sys:String>
<sys:String x:Key="Speed.Progress.Speed">Download speed</sys:String>
<sys:String x:Key="Speed.Progress.Total">Total progress</sys:String>
<!-- SystemDialog -->
<sys:String x:Key="SystemDialog.Browser.OpenFailed.Message">PCL CE could not open this link. The link was copied to the clipboard and can be opened manually.&#xA;&#xA;Link: {0}</sys:String>
<sys:String x:Key="SystemDialog.Browser.OpenFailed.Title">Failed to open link</sys:String>
<sys:String x:Key="SystemDialog.Fatal.Message.WithFeedbackGuidance">{0}&#xA;&#xA;If you think this is a launcher issue, please submit feedback so it can be investigated.&#xA;Export logs: Settings → View logs → Export all logs</sys:String>
<sys:String x:Key="SystemDialog.Fatal.Title">Fatal error</sys:String>
<sys:String x:Key="SystemDialog.File.AllFilesFilter">All files|*.*</sys:String>
<sys:String x:Key="SystemDialog.File.OpenFailed.Message">Failed to open the file or program.&#xA;&#xA;Path: {0}</sys:String>
<sys:String x:Key="SystemDialog.File.SelectTitle">Select file</sys:String>
<sys:String x:Key="SystemDialog.Folder.OpenFailed.Message">Failed to open File Explorer. Try closing security software, such as 360 Total Security.&#xA;&#xA;Path: {0}</sys:String>
<sys:String x:Key="SystemDialog.Folder.SelectTitle">Select folder</sys:String>
<sys:String x:Key="SystemDialog.Startup.DotNetRuntimeOutdated.Message">Your .NET Desktop Runtime is outdated or corrupted. Please reinstall .NET 10.</sys:String>
<sys:String x:Key="SystemDialog.Startup.InitializationTitle">PCL CE failed to initialize</sys:String>
<!-- SystemDialog.Error -->
<sys:String x:Key="SystemDialog.Error.Detail.WithException">{0}&#xA;&#xA;Details:&#xA;{1}</sys:String>
<sys:String x:Key="SystemDialog.Error.Message.WithLogExportGuidance">{0}&#xA;&#xA;When asking for help, do not close launcher. Export the logs now (Settings → View logs → Export logs), then send the exported log archive. A screenshot of this window alone is usually not enough to diagnose the problem.</sys:String>
<sys:String x:Key="SystemDialog.Error.OperationFailed.Message">An error occurred while performing this operation.&#xA;&#xA;Operation:&#xA;{0}&#xA;&#xA;Details:&#xA;{1}</sys:String>
<sys:String x:Key="SystemDialog.Error.OperationFailed.RuntimeMessage">An error occurred while performing this operation.&#xA;&#xA;Operation:&#xA;{0}&#xA;&#xA;Details:&#xA;{1}&#xA;&#xA;A low-level system operation failed. Please try reinstalling .NET 10.</sys:String>
<sys:String x:Key="SystemDialog.Error.Title">Error</sys:String>
<sys:String x:Key="SystemDialog.Error.Unexpected.Message">An unexpected error occurred.</sys:String>
<sys:String x:Key="SystemDialog.Error.Unexpected.Title">Unexpected error</sys:String>
<sys:String x:Key="SystemDialog.Error.UserVisible.Message">An error occurred.&#xA;&#xA;Details:&#xA;{0}</sys:String>
<!-- Tools -->
<sys:String x:Key="Tools.Error.OperationFailed">A utilities operation failed. See the details below.</sys:String>
<sys:String x:Key="Tools.Left.Lobby">Lobby</sys:String>
<sys:String x:Key="Tools.Left.Multiplayer">Multiplayer</sys:String>
<sys:String x:Key="Tools.Left.Utilities">Utilities</sys:String>
<sys:String x:Key="Tools.Left.Utilities.Title">Toolbox</sys:String>
<!-- Tools.GameLink -->
<sys:String x:Key="Tools.GameLink.Announcement.Format">[{0}] {1}</sys:String>
<sys:String x:Key="Tools.GameLink.Announcement.Important">Important</sys:String>
<sys:String x:Key="Tools.GameLink.Announcement.Notice">Notice</sys:String>
<sys:String x:Key="Tools.GameLink.Announcement.Warning">Warning</sys:String>
<sys:String x:Key="Tools.GameLink.CopyIp.Back">Back</sys:String>
<sys:String x:Key="Tools.GameLink.CopyIp.Message">Lobby host's game address: {0}&#xA;Note: IP connection is only recommended when the lobby broadcast does not appear in the Minecraft multiplayer list. IP connection may require a licensed account.</sys:String>
<sys:String x:Key="Tools.GameLink.CopyIp.Title">Copy IP</sys:String>
<sys:String x:Key="Tools.GameLink.Create.Action">Create</sys:String>
<sys:String x:Key="Tools.GameLink.Create.Description">1. After entering a world, select "Open to LAN" in the game menu&#xA;2. Select this game instance below and click "Create"&#xA;3. After the lobby is created, copy the lobby ID and send it to your friend</sys:String>
<sys:String x:Key="Tools.GameLink.Create.EnterPort">Enter port</sys:String>
<sys:String x:Key="Tools.GameLink.Create.ManualInput">Manual input</sys:String>
<sys:String x:Key="Tools.GameLink.Create.NoWorld">Please select a world to play together!</sys:String>
<sys:String x:Key="Tools.GameLink.Create.NotMcPort">This doesn't seem to be a Minecraft service port...</sys:String>
<sys:String x:Key="Tools.GameLink.Create.Title">Create lobby</sys:String>
<sys:String x:Key="Tools.GameLink.EasyTierVersion">Both sides are using different launcher versions, which may cause connection issues.&#xA;We strongly recommend that both sides upgrade to the latest version before continuing.</sys:String>
<sys:String x:Key="Tools.GameLink.Error.ConnectFailed">Failed to connect to the lobby server</sys:String>
<sys:String x:Key="Tools.GameLink.Error.NetworkTestFailed">Failed to get network test results. See the details below.</sys:String>
<sys:String x:Key="Tools.GameLink.Error.ServerExit">An error occurred while exiting the server!</sys:String>
<sys:String x:Key="Tools.GameLink.Error.ServerMessage">A lobby server error occurred.&#xA;&#xA;Original error:&#xA;{0}</sys:String>
<sys:String x:Key="Tools.GameLink.Error.UpdateRequired">Please update to the latest PCL CE to use lobby features.</sys:String>
<sys:String x:Key="Tools.GameLink.Eula.Disabled">Lobby feature disabled!</sys:String>
<sys:String x:Key="Tools.GameLink.Eula.RevokeConfirm">Are you sure you want to revoke the lobby agreement authorization?</sys:String>
<sys:String x:Key="Tools.GameLink.Eula.RevokeTitle">Revoke authorization</sys:String>
<sys:String x:Key="Tools.GameLink.Exit.ConfirmMessage">Are you sure you want to exit the lobby?</sys:String>
<sys:String x:Key="Tools.GameLink.Exit.ConfirmMessageWithHost">Are you sure you want to exit the lobby?&#xA;Since you are the lobby host, exiting will automatically disband this lobby.</sys:String>
<sys:String x:Key="Tools.GameLink.Exit.ConfirmTitle">Confirm exit</sys:String>
<sys:String x:Key="Tools.GameLink.Exit.Disbanded">The lobby has been automatically disbanded because you closed the Minecraft instance in the lobby.</sys:String>
<sys:String x:Key="Tools.GameLink.Exit.DisbandedTitle">Lobby disbanded</sys:String>
<sys:String x:Key="Tools.GameLink.Exit.HostWarning">Since you are the lobby host, exiting will automatically disband this lobby.</sys:String>
<sys:String x:Key="Tools.GameLink.Join.Action">Join</sys:String>
<sys:String x:Key="Tools.GameLink.Join.Clear">Clear</sys:String>
<sys:String x:Key="Tools.GameLink.Join.Description">1. Enter the lobby ID sent by your friend below and click "Join"&#xA;2. Launch the game, select "Multiplayer", and join the lobby in LAN games</sys:String>
<sys:String x:Key="Tools.GameLink.Join.IdHint">ID should look like: U/NNNN-AAAA-SSSS-EEEE</sys:String>
<sys:String x:Key="Tools.GameLink.Join.InvalidText">Invalid lobby ID. Please check and enter it again.</sys:String>
<sys:String x:Key="Tools.GameLink.Join.Paste">Paste</sys:String>
<sys:String x:Key="Tools.GameLink.Join.Title">Join lobby</sys:String>
<sys:String x:Key="Tools.GameLink.Link.Disable">Disable lobby</sys:String>
<sys:String x:Key="Tools.GameLink.Link.EasyTier">EasyTier official website</sys:String>
<sys:String x:Key="Tools.GameLink.Link.Friends">Links: </sys:String>
<sys:String x:Key="Tools.GameLink.Link.Privacy">Lobby Privacy Policy</sys:String>
<sys:String x:Key="Tools.GameLink.Link.Report">Report violation</sys:String>
<sys:String x:Key="Tools.GameLink.Link.Terms">Natayark OpenID ToS</sys:String>
<sys:String x:Key="Tools.GameLink.Loading.ConnectingServer">Connecting to lobby server...</sys:String>
<sys:String x:Key="Tools.GameLink.Loading.Creating">Creating lobby</sys:String>
<sys:String x:Key="Tools.GameLink.Loading.DownloadingModule">Downloading lobby module</sys:String>
<sys:String x:Key="Tools.GameLink.Member.ListCount">Lobby members ({0} total)</sys:String>
<sys:String x:Key="Tools.GameLink.Member.ListLoading">Lobby members (retrieving information)</sys:String>
<sys:String x:Key="Tools.GameLink.Nat.Failed">Test failed</sys:String>
<sys:String x:Key="Tools.GameLink.Nat.Result">UDP: {0}, TCP: {1}</sys:String>
<sys:String x:Key="Tools.GameLink.Nat.Test">Click to test</sys:String>
<sys:String x:Key="Tools.GameLink.Nat.Testing">Testing</sys:String>
<sys:String x:Key="Tools.GameLink.Nat.Title">NAT type</sys:String>
<sys:String x:Key="Tools.GameLink.Player.Details">{0} {1}</sys:String>
<sys:String x:Key="Tools.GameLink.Player.Host">[Host]</sys:String>
<sys:String x:Key="Tools.GameLink.Player.InfoMessage">Username: {0}&#xA;Client identifier: {1}&#xA;&#xA;This data is for reference only. Actual gameplay may vary.&#xA;&#xA;To learn about NAT types and how they affect your lobby experience, see the FAQ section on the left.</sys:String>
<sys:String x:Key="Tools.GameLink.Player.InfoTitle">Player {0} details</sys:String>
<!-- Tools.GameLink.Agreement -->
<sys:String x:Key="Tools.GameLink.Agreement.Accept">I have read and agree</sys:String>
<sys:String x:Key="Tools.GameLink.Agreement.Body">By using the lobby feature, you agree to the following terms:&#xA;&#xA;I agree to comply with applicable laws and regulations and will not use the lobby feature for illegal or prohibited purposes.&#xA;I agree to bear all risks associated with using the lobby feature.&#xA;I acknowledge and agree that PCL CE collects processed device identifiers, Natayark ID, and other information, and may provide them to law enforcement when necessary.&#xA;To protect minors' personal information, I confirm that I am at least 14 years old before using the lobby.&#xA;&#xA;Additionally, you must also agree to the PCL CE lobby privacy policy and the Natayark OpenID terms of service.</sys:String>
<sys:String x:Key="Tools.GameLink.Agreement.Description">Service documents and introductions used by PCL CE Lobby are listed below.</sys:String>
<sys:String x:Key="Tools.GameLink.Agreement.NatayarkTerms">Natayark OpenID ToS</sys:String>
<sys:String x:Key="Tools.GameLink.Agreement.NatayarkTermsInfo">View Natayark OpenID terms of service.</sys:String>
<sys:String x:Key="Tools.GameLink.Agreement.PrivacyPolicy">PCL CE Lobby Privacy Policy</sys:String>
<sys:String x:Key="Tools.GameLink.Agreement.PrivacyPolicyInfo">Learn how PCL CE handles your personal information.</sys:String>
<sys:String x:Key="Tools.GameLink.Agreement.Title">PCL CE Lobby Terms &amp; Conditions</sys:String>
<!-- Tools.GameLink.Finish -->
<sys:String x:Key="Tools.GameLink.Finish.Actions">Lobby actions</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.Checking">Checking</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.CloseLobby">Close lobby</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.Connected">Connected</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.Connecting">Connecting</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.ConnectionStatus">Connection status</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.ConnectionType">Connection type</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.CopyLobbyId">Copy lobby ID</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.CopyVirtualIp">Copy virtual IP</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.Exit">Exit lobby</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.Guest">Guest</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.Host">Host</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.InfoTitle">Lobby information</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.LobbyId">Lobby ID</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.PingMs">{0}ms</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.PingToHost">Ping to lobby host</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.Unavailable">Unavailable</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.UserName">Username</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.UserType">User type</sys:String>
<!-- Tools.GameLink.Natayark -->
<sys:String x:Key="Tools.GameLink.Natayark.Abnormal">(Abnormal)</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.Account">Natayark account</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.AccountExpired">Natayark account information has expired. Please log in again in Settings!</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.BrowserContinue">Please continue in your browser...</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.Continue">Continue</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.FetchFailed">Failed to fetch information</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.InvalidAuthCode">NAID authorization code is invalid.</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.Loading">Loading...</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.Login">Log in to Natayark</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.LoginComplete">Login completed</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.LoginFailed">NAID login failed. Please log in again in Settings.</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.LoginPrompt">PCL CE will open a login page. Please complete the login in your browser, then return to the launcher to continue.</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.LoginTitle">Log in to Natayark Network</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.LogoutComplete">Logged out!</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.LogoutConfirm">Are you sure you want to log out?</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.LogoutTitle">Log out</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.ProfileLoadFailed">Failed to load Natayark user data. Please log in again in Settings.</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.TokenExpired">Natayark ID token has expired. Please log in again.</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.TokenInvalid">NAID access token is invalid. Try refreshing your login.</sys:String>
<!-- Tools.ServerQuery -->
<sys:String x:Key="Tools.ServerQuery.Action.Query">Query</sys:String>
<sys:String x:Key="Tools.ServerQuery.Address.Hint">Enter server address</sys:String>
<sys:String x:Key="Tools.ServerQuery.State.NoInfo">No server information</sys:String>
<sys:String x:Key="Tools.ServerQuery.State.Querying">Querying...</sys:String>
<sys:String x:Key="Tools.ServerQuery.Title.MinecraftServer">Minecraft server</sys:String>
<!-- Tools.ServerQuery.Error -->
<sys:String x:Key="Tools.ServerQuery.Error.AddressTooLong">Server address too long</sys:String>
<sys:String x:Key="Tools.ServerQuery.Error.EmptyPacket">Server returned empty data packet</sys:String>
<sys:String x:Key="Tools.ServerQuery.Error.IncompleteConnection">Server disconnected before returning the complete status packet.</sys:String>
<sys:String x:Key="Tools.ServerQuery.Error.InvalidResponse">Server returned invalid information</sys:String>
<sys:String x:Key="Tools.ServerQuery.Error.PongDataLength">Pong data must be exactly 8 bytes</sys:String>
<sys:String x:Key="Tools.ServerQuery.Error.StaleConnection">Server disconnected before returning the pong packet, so latency could not be calculated.</sys:String>
<sys:String x:Key="Tools.ServerQuery.Error.Timeout.Connect">Connection timeout</sys:String>
<sys:String x:Key="Tools.ServerQuery.Error.Timeout.ReadWrite">Read/Write timeout</sys:String>
<sys:String x:Key="Tools.ServerQuery.Error.UnableToConnect">Unable to connect: {0}</sys:String>
<!-- Tools.Test -->
<sys:String x:Key="Tools.Test.CeNotice">For ease of maintenance, this feature is not included in the Community Edition...</sys:String>
<sys:String x:Key="Tools.Test.Crash.ManualCrash">Manual crash</sys:String>
<sys:String x:Key="Tools.Test.Crash.Title">Crash test</sys:String>
<sys:String x:Key="Tools.Test.Dependency.Checking">Checking environment...</sys:String>
<sys:String x:Key="Tools.Test.Dependency.Later">Later</sys:String>
<sys:String x:Key="Tools.Test.Dependency.MissingMessage">Required dependency "{0}" is missing.&#xA;&#xA;Click OK to open Microsoft Store.</sys:String>
<sys:String x:Key="Tools.Test.Dependency.Title">Dependency check</sys:String>
<sys:String x:Key="Tools.Test.Error.OperationFailed">A test utility operation failed. See the details below.</sys:String>
<sys:String x:Key="Tools.Test.LaunchCount.Message">PCL CE has launched games for you {0} times.</sys:String>
<sys:String x:Key="Tools.Test.LaunchCount.Title">View launch count</sys:String>
<sys:String x:Key="Tools.Test.ServerQuery.Title">Check server</sys:String>
<sys:String x:Key="Tools.Test.Title">Utilities</sys:String>
<!-- Tools.Test.Achievement -->
<sys:String x:Key="Tools.Test.Achievement.FetchFailed">Failed to get achievement image. Please check for special characters.</sys:String>
<sys:String x:Key="Tools.Test.Achievement.FileFilter">PNG image (*.png)|*.png</sys:String>
<sys:String x:Key="Tools.Test.Achievement.InvalidId">Please enter a valid item ID!</sys:String>
<sys:String x:Key="Tools.Test.Achievement.ItemId">Item name (ID)</sys:String>
<sys:String x:Key="Tools.Test.Achievement.Line1">Line 1</sys:String>
<sys:String x:Key="Tools.Test.Achievement.Line2">Line 2 (optional)</sys:String>
<sys:String x:Key="Tools.Test.Achievement.Name">Achievement name</sys:String>
<sys:String x:Key="Tools.Test.Achievement.Preview">Preview achievement</sys:String>
<sys:String x:Key="Tools.Test.Achievement.Save">Save image</sys:String>
<sys:String x:Key="Tools.Test.Achievement.Saved">Custom achievement image saved!</sys:String>
<sys:String x:Key="Tools.Test.Achievement.Title">Custom achievement image generator (English only)</sys:String>
<!-- Tools.Test.Avatar -->
<sys:String x:Key="Tools.Test.Avatar.FileFilter">Image file (*.png)|*.png</sys:String>
<sys:String x:Key="Tools.Test.Avatar.GenerateFailed">Failed to generate avatar: {0}</sys:String>
<sys:String x:Key="Tools.Test.Avatar.Generated">Avatar generated!</sys:String>
<sys:String x:Key="Tools.Test.Avatar.InvalidSize">Image size is incorrect! Please confirm you selected the correct file!</sys:String>
<sys:String x:Key="Tools.Test.Avatar.Save">Save avatar</sys:String>
<sys:String x:Key="Tools.Test.Avatar.Saved">Avatar saved!</sys:String>
<sys:String x:Key="Tools.Test.Avatar.SelectFirst">Please select a skin first!</sys:String>
<sys:String x:Key="Tools.Test.Avatar.SelectSkin">Select skin</sys:String>
<sys:String x:Key="Tools.Test.Avatar.SelectSkinFile">Select skin file</sys:String>
<sys:String x:Key="Tools.Test.Avatar.Size">Avatar size:</sys:String>
<sys:String x:Key="Tools.Test.Avatar.Title">Skin avatar generator</sys:String>
<!-- Tools.Test.Clean -->
<sys:String x:Key="Tools.Test.Clean.Cleared">Cache cleared</sys:String>
<sys:String x:Key="Tools.Test.Clean.ClearedMessage">Cleaned {0} files!&#xA;PCL CE will restart...</sys:String>
<sys:String x:Key="Tools.Test.Clean.CloseGameFirst">Please close all running games first...</sys:String>
<sys:String x:Key="Tools.Test.Clean.ConfirmMessage">About to clean game logs, error reports, caches, and other files.&#xA;These folders should not contain important files, but are you sure you want to continue?&#xA;&#xA;PCL CE will restart automatically after cleanup.</sys:String>
<sys:String x:Key="Tools.Test.Clean.ConfirmTitle">Confirm cleanup</sys:String>
<sys:String x:Key="Tools.Test.Clean.NoFiles">No files to clean!</sys:String>
<sys:String x:Key="Tools.Test.Clean.Title">Clean game junk</sys:String>
<sys:String x:Key="Tools.Test.Clean.ToolTip">Clean PCL CE cache, Minecraft logs, crash reports, and other junk files</sys:String>
<sys:String x:Key="Tools.Test.Clean.WaitForDownload">Please wait for all downloads to complete before cleaning...</sys:String>
<!-- Tools.Test.CustomDownload -->
<sys:String x:Key="Tools.Test.CustomDownload.Aborted">{0} aborted!</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.Description">Use PCL CE's high-speed multi-threaded download engine to download any file. Note: some websites may return 403 Forbidden errors and cannot be downloaded.</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.FileName">File name</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.Finished">{0} completed!</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.LoaderName">Custom download file: {0} </sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.LoaderTitle">Custom download ({0}) </sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.SaveTo">Save to</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.Select">Select</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.SelectLocation">Select save location</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.Start">Start download</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.Title">Custom download</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.Url">Download URL</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.UserAgent">User-Agent</sys:String>
<!-- Tools.Test.Luck -->
<sys:String x:Key="Tools.Test.Luck.MessageBad">Your luck value is: {0}... {1}</sys:String>
<sys:String x:Key="Tools.Test.Luck.MessageGood">Your luck value is: {0}! {1}</sys:String>
<sys:String x:Key="Tools.Test.Luck.MsgboxTitle">Today's luck - {0}</sys:String>
<sys:String x:Key="Tools.Test.Luck.Rating0">(It's on a 0-100 scale)</sys:String>
<sys:String x:Key="Tools.Test.Luck.Rating10">No way!</sys:String>
<sys:String x:Key="Tools.Test.Luck.Rating100">100! 100!&#xA;Hidden theme... oh wait, CE probably doesn't have this...</sys:String>
<sys:String x:Key="Tools.Test.Luck.Rating30">Aww...</sys:String>
<sys:String x:Key="Tools.Test.Luck.Rating40">Barely okay...</sys:String>
<sys:String x:Key="Tools.Test.Luck.Rating60">Not bad, not bad</sys:String>
<sys:String x:Key="Tools.Test.Luck.Rating90">Overwhelmingly positive!</sys:String>
<sys:String x:Key="Tools.Test.Luck.Rating95">So close to 100...</sys:String>
<sys:String x:Key="Tools.Test.Luck.Title">Today's luck</sys:String>
<!-- Tools.Test.Shortcut -->
<sys:String x:Key="Tools.Test.Shortcut.ConfirmMessage">This shortcut will not be automatically removed. Please remove it manually before deleting/moving the launcher.&#xA;&#xA;{0} location: {1}&#xA;{2} location: {3}</sys:String>
<sys:String x:Key="Tools.Test.Shortcut.Created">Shortcut created on {0}</sys:String>
<sys:String x:Key="Tools.Test.Shortcut.Desktop">Desktop</sys:String>
<sys:String x:Key="Tools.Test.Shortcut.FileName">PCL CE{0}</sys:String>
<sys:String x:Key="Tools.Test.Shortcut.SelectLocation">Select shortcut location</sys:String>
<sys:String x:Key="Tools.Test.Shortcut.StartMenu">Start menu</sys:String>
<sys:String x:Key="Tools.Test.Shortcut.Title">Create shortcut</sys:String>
<sys:String x:Key="Tools.Test.Shortcut.ToolTip">Create a shortcut to the PCL CE executable</sys:String>
<!-- Tools.Test.Skin -->
<sys:String x:Key="Tools.Test.Skin.Fetching">Fetching skin...</sys:String>
<sys:String x:Key="Tools.Test.Skin.FileFilter">Skin image file (*.png)|*.png</sys:String>
<sys:String x:Key="Tools.Test.Skin.InvalidId">This is not a valid ID...</sys:String>
<sys:String x:Key="Tools.Test.Skin.PlayerName">Player name</sys:String>
<sys:String x:Key="Tools.Test.Skin.Save">Save skin</sys:String>
<sys:String x:Key="Tools.Test.Skin.Saved">Player {0}'s skin saved!</sys:String>
<sys:String x:Key="Tools.Test.Skin.Title">Download a player's official skin</sys:String>
<sys:String x:Key="Tools.Test.Skin.TooFrequent">Skin fetch has been rate-limited. Please try again in 5 minutes!</sys:String>
<!-- Update -->
<sys:String x:Key="Update.Action">Update</sys:String>
<sys:String x:Key="Update.Available">A new launcher version is available ({0} -&gt; {1})&#xA;Update now?</sys:String>
<sys:String x:Key="Update.Check.Failed">Unable to get latest version information. Please check your network connection.</sys:String>
<sys:String x:Key="Update.CommunityNotice.Body">You are using PCL CE from PCL-Community. Please DO NOT report issues to the original repository!&#xA;PCL-Community and its members are not affiliated with LTCat (龙腾猫跃), and do not guarantee your usage.&#xA;&#xA;If you downloaded PCL CE by mistake, we recommend using the original PCL instead.&#xA;If you downloaded PCL CE by mistake, we recommend using the original PCL instead.&#xA;If you downloaded PCL CE by mistake, we recommend using the original PCL instead.&#xA;&#xA;Differences from the original version:&#xA;- Themes: only some fixed blue themes; no plans to add new themes.&#xA;- Toolbox: some content from the original version is missing (Echo Cave, Don't Click).&#xA;&#xA;This message will be shown once after a launcher update.</sys:String>
<sys:String x:Key="Update.CommunityNotice.Confirm">I understand</sys:String>
<sys:String x:Key="Update.CommunityNotice.Title">PCL CE notice</sys:String>
<sys:String x:Key="Update.Error.FetchFailed">Failed to get launcher update. Please check your network connection.</sys:String>
<sys:String x:Key="Update.Error.FileNotFound">Update file not found</sys:String>
<sys:String x:Key="Update.Error.OperationFailed">An update operation failed. See the details below.</sys:String>
<sys:String x:Key="Update.Error.Sha256Mismatch">Update file SHA256 mismatch. Expected: {0}, actual: {1}</sys:String>
<sys:String x:Key="Update.Error.UnableToGetUpdate">Unable to get update information</sys:String>
<sys:String x:Key="Update.Error.UpdateBlocked">Update failed</sys:String>
<sys:String x:Key="Update.Error.UpdateBlockedMessage">PCL CE cannot update because it was blocked by Windows Security or does not have sufficient permissions.&#xA;Please add the PCL CE folder to the exclusion list, or manually replace the current file with {0}PCL\Plain Craft Launcher Community Edition.exe!</sys:String>
<sys:String x:Key="Update.Service.PclCe">PCL CE service</sys:String>
<sys:String x:Key="Update.Title">Launcher update</sys:String>
<!-- Update.Task -->
<sys:String x:Key="Update.Task.ApplyFile">Apply file</sys:String>
<sys:String x:Key="Update.Task.Check">Verify update</sys:String>
<sys:String x:Key="Update.Task.DownloadFile">Download file</sys:String>
<sys:String x:Key="Update.Task.DownloadLatestStable">Download latest stable</sys:String>
<sys:String x:Key="Update.Task.DownloadUpdateFile">Download update file</sys:String>
<sys:String x:Key="Update.Task.GetAnnouncementFailed">Failed to get announcement information</sys:String>
<sys:String x:Key="Update.Task.GetDownloadInfo">Get download information</sys:String>
<sys:String x:Key="Update.Task.GetVersionInfo">Get version information</sys:String>
<sys:String x:Key="Update.Task.GetVersionInfoFailed">Failed to get version information</sys:String>
<sys:String x:Key="Update.Task.Install">Install update</sys:String>
<sys:String x:Key="Update.Task.Prepare">Prepare update</sys:String>
<sys:String x:Key="Update.Task.RefreshSettings">Refresh settings UI</sys:String>
<sys:String x:Key="Update.Task.RestartInstall">Restart install</sys:String>
<sys:String x:Key="Update.Task.ShowButton">Show button</sys:String>
<!-- Watcher -->
<sys:String x:Key="Watcher.Crash.AbnormalExit">Minecraft exited with an abnormal code and may have crashed.</sys:String>
<sys:String x:Key="Watcher.Crash.AnalysisStart">Crash analysis started</sys:String>
<sys:String x:Key="Watcher.Crash.Detected">Minecraft has crashed. Crash analysis will start in 2 seconds.</sys:String>
<sys:String x:Key="Watcher.Crash.DetectedDisabled">Minecraft has crashed. Since crash analysis has been disabled, analysis will not be started.</sys:String>
<sys:String x:Key="Watcher.Crash.Hint">Minecraft error detected. Starting analysis...</sys:String>
<sys:String x:Key="Watcher.Crash.Suspected">Minecraft hasn't finished loading and may have crashed.</sys:String>
<sys:String x:Key="Watcher.Exited">Minecraft log monitoring exited</sys:String>
<sys:String x:Key="Watcher.FmlWindowLoaded">FML window loaded: {0} ({1})</sys:String>
<sys:String x:Key="Watcher.Kill.Attempt">Attempting to force-kill the Minecraft process</sys:String>
<sys:String x:Key="Watcher.Kill.Failed">Failed to force-kill the Minecraft process</sys:String>
<sys:String x:Key="Watcher.Kill.Success">Minecraft process force-killed</sys:String>
<sys:String x:Key="Watcher.Kill.TaskkillAttempt">Process is still alive. Trying taskkill.exe</sys:String>
<sys:String x:Key="Watcher.Kill.TaskkillResult">taskkill.exe result: {0}</sys:String>
<sys:String x:Key="Watcher.Kill.Timeout">Force-kill failed: process exit timed out</sys:String>
<sys:String x:Key="Watcher.LoadComplete">Minecraft loading completed</sys:String>
<sys:String x:Key="Watcher.Log.CloseDetected">Detected close log: {0}</sys:String>
<sys:String x:Key="Watcher.Log.CrashDetected">Detected crash log: {0}</sys:String>
<sys:String x:Key="Watcher.ProcessExited">Minecraft exited with code: {0}</sys:String>
<sys:String x:Key="Watcher.Progress.LogAppeared">Log 1/5: Log output appeared</sys:String>
<sys:String x:Key="Watcher.Progress.LwjglConfirmed">Log 3/5: LWJGL version confirmed</sys:String>
<sys:String x:Key="Watcher.Progress.OpenAlLoaded">Log 4/5: OpenAL loaded</sys:String>
<sys:String x:Key="Watcher.Progress.TexturesLoaded">Log 5/5: Textures loaded</sys:String>
<sys:String x:Key="Watcher.Progress.UserSet">Log 2/5: Game user set</sys:String>
<sys:String x:Key="Watcher.SecurityBlocked">PCL CE cannot access the game window due to anti-cheat or security software</sys:String>
<sys:String x:Key="Watcher.Start">Started Minecraft log monitoring</sys:String>
<sys:String x:Key="Watcher.WindowLoaded">Minecraft window loaded: {0} ({1})</sys:String>
<sys:String x:Key="Watcher.WindowMaximized">Minecraft window maximized: {0}</sys:String>
</ResourceDictionary>
@@ -0,0 +1,13 @@
<!-- Formatted by xaml-lang-formatter at 2026-06-28T17:16:52. -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xml:space="preserve">
<!-- Meta -->
<sys:String x:Key="Meta.Code">es-ES</sys:String>
<sys:String x:Key="Meta.Name">Español (España)</sys:String>
</ResourceDictionary>
@@ -0,0 +1,13 @@
<!-- Formatted by xaml-lang-formatter at 2026-06-28T17:16:52. -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xml:space="preserve">
<!-- Meta -->
<sys:String x:Key="Meta.Code">fr-FR</sys:String>
<sys:String x:Key="Meta.Name">Français (France)</sys:String>
</ResourceDictionary>
@@ -0,0 +1,13 @@
<!-- Formatted by xaml-lang-formatter at 2026-06-28T17:16:52. -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xml:space="preserve">
<!-- Meta -->
<sys:String x:Key="Meta.Code">ja-JP</sys:String>
<sys:String x:Key="Meta.Name">日本語(日本)</sys:String>
</ResourceDictionary>
@@ -0,0 +1,3573 @@
<!-- Formatted by xaml-lang-formatter at 2026-06-28T17:16:52. -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xml:space="preserve">
<!-- Meta -->
<sys:String x:Key="Meta.Code">zh-CN</sys:String>
<sys:String x:Key="Meta.Name">简体中文(中国大陆)</sys:String>
<!-- Application -->
<sys:String x:Key="Application.Animation.Error.OperationFailed">动画操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Application.Control.Error.OperationFailed">界面控件操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Application.Control.Loading.Default">加载中...</sys:String>
<sys:String x:Key="Application.Control.Loading.Failed">加载失败</sys:String>
<sys:String x:Key="Application.Control.Loading.NetworkUnstable">网络环境不佳,请稍后重试,或使用 VPN 以改善网络环境。</sys:String>
<sys:String x:Key="Application.Control.Loading.UnknownError">未知错误</sys:String>
<sys:String x:Key="Application.Control.MessageBox.Error.OperationFailed">消息弹窗操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Application.EnvironmentWarning.IKnow">我知道了</sys:String>
<sys:String x:Key="Application.EnvironmentWarning.Message">PCL CE 在启动时检测到环境问题:&#xA;&#xA;{0}&#xA;&#xA;不解决这些问题可能会导致部分功能无法正常工作……</sys:String>
<sys:String x:Key="Application.EnvironmentWarning.SocialSoftwareFolder">- PCL CE 正在 QQ、微信、TIM 等社交软件的下载目录运行,请考虑移动到其他位置,否则可能导致游戏存档或设置丢失</sys:String>
<sys:String x:Key="Application.EnvironmentWarning.System32Bit">- 当前系统为 32 位,不受 PCL CE 和新版 Minecraft 支持,非常建议重装为 64 位系统后再进行游戏</sys:String>
<sys:String x:Key="Application.EnvironmentWarning.TempFolder">- PCL CE 正在临时目录运行,请将 PCL CE 从压缩包中解压之后再使用,否则可能导致游戏存档或设置丢失</sys:String>
<sys:String x:Key="Application.EnvironmentWarning.Title">环境警告</sys:String>
<sys:String x:Key="Application.EnvironmentWarning.WindowsVersion">- Windows 版本不满足推荐要求,推荐至少 Windows 10 1809,建议考虑升级 Windows 系统</sys:String>
<sys:String x:Key="Application.InitializationError.Path">PCL CE 所在路径:{0}</sys:String>
<sys:String x:Key="Application.InitializationError.PathUnavailable">获取失败</sys:String>
<sys:String x:Key="Application.InitializationError.Title">PCL CE 初始化错误</sys:String>
<sys:String x:Key="Application.Loader.Error.OperationFailed">任务加载操作失败。请查看错误详情。</sys:String>
<!-- Common -->
<sys:String x:Key="Common.Dialog.Title">提示</sys:String>
<sys:String x:Key="Common.Dialog.Warning">警告</sys:String>
<sys:String x:Key="Common.Error.InvalidJson">不是有效的 JSON 文件</sys:String>
<sys:String x:Key="Common.Format.DownloadCount">下载 {0} 次</sys:String>
<sys:String x:Key="Common.Format.Number.Digit1">{0} 万</sys:String>
<sys:String x:Key="Common.Format.Number.Digit2">{0} 亿</sys:String>
<sys:String x:Key="Common.Format.Number.Digit3">{0} 兆</sys:String>
<sys:String x:Key="Common.Format.TimeSpan.Future">{0}后</sys:String>
<sys:String x:Key="Common.Format.TimeSpan.Past">{0}前</sys:String>
<sys:String x:Key="Common.Hint.Copied">已成功复制!</sys:String>
<sys:String x:Key="Common.Hint.CopyFailed">无法复制文本,剪贴板可能正被其他程序占用。</sys:String>
<sys:String x:Key="Common.Hint.DoNotShowAgain">不再提示</sys:String>
<sys:String x:Key="Common.Hint.FilesPasted">已粘贴 {0} 个文件和 {1} 个文件夹。</sys:String>
<sys:String x:Key="Common.Option.All">全部</sys:String>
<sys:String x:Key="Common.Option.Auto">自动</sys:String>
<sys:String x:Key="Common.Option.Customize">自定义</sys:String>
<sys:String x:Key="Common.Option.Default">默认</sys:String>
<sys:String x:Key="Common.Option.IDontKnow">我不知道</sys:String>
<sys:String x:Key="Common.Option.No">否</sys:String>
<sys:String x:Key="Common.Option.Yes">是</sys:String>
<sys:String x:Key="Common.State.Loading">加载中...</sys:String>
<sys:String x:Key="Common.State.Unknown">未知</sys:String>
<sys:String x:Key="Common.Status.Cancelled">已取消!</sys:String>
<sys:String x:Key="Common.Status.Failure">失败:</sys:String>
<sys:String x:Key="Common.Status.Success">成功!</sys:String>
<!-- Common.Action -->
<sys:String x:Key="Common.Action.Agree">同意</sys:String>
<sys:String x:Key="Common.Action.Back">返回</sys:String>
<sys:String x:Key="Common.Action.Cancel">取消</sys:String>
<sys:String x:Key="Common.Action.Close">关闭</sys:String>
<sys:String x:Key="Common.Action.Confirm">确定</sys:String>
<sys:String x:Key="Common.Action.Continue">继续</sys:String>
<sys:String x:Key="Common.Action.Copy">复制</sys:String>
<sys:String x:Key="Common.Action.Decline">拒绝</sys:String>
<sys:String x:Key="Common.Action.Delete">删除</sys:String>
<sys:String x:Key="Common.Action.Download">下载</sys:String>
<sys:String x:Key="Common.Action.GotIt">我知道了</sys:String>
<sys:String x:Key="Common.Action.Initialize">初始化</sys:String>
<sys:String x:Key="Common.Action.Install">安装</sys:String>
<sys:String x:Key="Common.Action.Open">打开</sys:String>
<sys:String x:Key="Common.Action.OpenFolder">打开文件夹</sys:String>
<sys:String x:Key="Common.Action.Overwrite">覆盖</sys:String>
<sys:String x:Key="Common.Action.Refresh">刷新</sys:String>
<sys:String x:Key="Common.Action.Reset">重置</sys:String>
<sys:String x:Key="Common.Action.Search">搜索</sys:String>
<sys:String x:Key="Common.Action.UpdateLog">更新日志</sys:String>
<!-- Common.Installation -->
<sys:String x:Key="Common.Installation.Cleanroom">Cleanroom</sys:String>
<sys:String x:Key="Common.Installation.Fabric">Fabric</sys:String>
<sys:String x:Key="Common.Installation.FabricApi">Fabric API</sys:String>
<sys:String x:Key="Common.Installation.Forge">Forge</sys:String>
<sys:String x:Key="Common.Installation.Iris">Iris</sys:String>
<sys:String x:Key="Common.Installation.LabyMod">LabyMod</sys:String>
<sys:String x:Key="Common.Installation.LegacyFabric">Legacy Fabric</sys:String>
<sys:String x:Key="Common.Installation.LegacyFabricApi">Legacy Fabric API</sys:String>
<sys:String x:Key="Common.Installation.LiteLoader">LiteLoader</sys:String>
<sys:String x:Key="Common.Installation.Minecraft">Minecraft</sys:String>
<sys:String x:Key="Common.Installation.NeoForge">NeoForge</sys:String>
<sys:String x:Key="Common.Installation.OptiFabric">OptiFabric</sys:String>
<sys:String x:Key="Common.Installation.OptiFine">OptiFine</sys:String>
<sys:String x:Key="Common.Installation.Quilt">Quilt</sys:String>
<!-- Config -->
<sys:String x:Key="Config.Error.InvalidFormat.RecoveryMessage">配置初始化失败,当前位于 {0} 阶段。&#xA;&#xA;配置文件 {1} 的内容出现问题,已备份到 {2}。&#xA;为尽可能防止重复故障,若存在上一版本,PCL CE 已尝试将配置恢复至上一版本。若曾手动更改过配置文件,请修复问题并替换当前的配置文件。&#xA;&#xA;如果你不知道发生了什么,可以先无视。重新打开启动器后,相关配置项可能会恢复为默认值,但通常不影响正常使用。</sys:String>
<sys:String x:Key="Config.Error.LoadFailed.DebugMessage">配置初始化失败,当前位于 {0} 阶段。&#xA;&#xA;配置系统无法完成初始化。请修复问题后重启 PCL CE。</sys:String>
<sys:String x:Key="Config.Error.LoadFailed.Message">配置系统在 {0} 阶段发生错误。</sys:String>
<sys:String x:Key="Config.Error.LoadFailed.Title">配置初始化失败</sys:String>
<sys:String x:Key="Config.Error.SaveFailed.Message">无法保存配置文件。&#xA;&#xA;路径:{0}</sys:String>
<sys:String x:Key="Config.Error.SaveFailed.Title">保存配置文件失败</sys:String>
<!-- Crash -->
<sys:String x:Key="Crash.Analysis.Error.Failed">崩溃分析失败。请查看错误详情。</sys:String>
<sys:String x:Key="Crash.Dialog.Button.ExportReport">导出错误报告</sys:String>
<sys:String x:Key="Crash.Dialog.Button.GoToModify">前往修改</sys:String>
<sys:String x:Key="Crash.Dialog.Button.OpenLog">查看日志</sys:String>
<sys:String x:Key="Crash.Dialog.Title.Auto">Minecraft 出现错误</sys:String>
<sys:String x:Key="Crash.Dialog.Title.Manual">错误报告分析结果</sys:String>
<sys:String x:Key="Crash.Export.Failed.CopyDetails">复制详细信息</sys:String>
<sys:String x:Key="Crash.Export.Failed.Message">无法导出错误报告。&#xA;&#xA;目标路径:{0}</sys:String>
<sys:String x:Key="Crash.Export.Failed.MessageWithoutPath">无法导出错误报告,且未能获取目标路径。</sys:String>
<sys:String x:Key="Crash.Export.Failed.Title">导出错误报告失败</sys:String>
<sys:String x:Key="Crash.Presentation.ListItem"> - {0}</sys:String>
<sys:String x:Key="Crash.Presentation.ReasonOnly">原因&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Presentation.WithFollowUps">{0}&#xA;&#xA;{1}</sys:String>
<sys:String x:Key="Crash.Presentation.WithSuggestion">原因&#xA;{0}&#xA;&#xA;你可以尝试&#xA;{1}</sys:String>
<!-- Crash.Reason -->
<sys:String x:Key="Crash.Reason.ConfirmedMod.Multiple">以下模组导致了游戏出错:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.ConfirmedMod.Single">模组 {0} 导致了游戏出错。</sys:String>
<sys:String x:Key="Crash.Reason.DuplicateMods.Known">检测到重复安装的模组:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.DuplicateMods.Unknown">可能安装了重复的模组。</sys:String>
<sys:String x:Key="Crash.Reason.ExtractedModFile">检测到已被解压的模组文件。</sys:String>
<sys:String x:Key="Crash.Reason.FabricError.Generic">Fabric 可能已经提供了错误信息。</sys:String>
<sys:String x:Key="Crash.Reason.FabricError.WithDetail">Fabric 提供了以下错误信息:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.FabricSolution.Generic">Fabric 可能已经提供了解决方案。</sys:String>
<sys:String x:Key="Crash.Reason.FabricSolution.WithDetail">Fabric 提供了以下解决方案:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.FileOrContentValidationFailed">部分文件或内容未能通过校验。</sys:String>
<sys:String x:Key="Crash.Reason.ForgeError.Generic">Forge 可能已经提供了错误信息。</sys:String>
<sys:String x:Key="Crash.Reason.ForgeError.WithDetail">Forge 提供了以下错误信息:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.GraphicsDriver.Generic">显卡驱动可能导致游戏无法正常运行。</sys:String>
<sys:String x:Key="Crash.Reason.GraphicsDriver.IntelOrIntegrated">显卡驱动存在问题,或游戏未使用独立显卡。</sys:String>
<sys:String x:Key="Crash.Reason.IncompatibleMods.Generic">已安装的模组可能不兼容。</sys:String>
<sys:String x:Key="Crash.Reason.IncompatibleMods.WithDetail">已安装的模组不兼容:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.IncompleteForgeInstallation">Forge 安装不完整,缺少必要文件。</sys:String>
<sys:String x:Key="Crash.Reason.InvalidModFileName">某个模组文件名包含 Java 不支持的特殊字符。</sys:String>
<sys:String x:Key="Crash.Reason.JavaIncompatible">当前 Java 版本与游戏或模组不兼容。</sys:String>
<sys:String x:Key="Crash.Reason.JavaTooNew">当前 Java 版本对该游戏或加载器来说过高。</sys:String>
<sys:String x:Key="Crash.Reason.ManualDebugCrash">这是手动触发的调试崩溃,游戏本身通常没有问题。</sys:String>
<sys:String x:Key="Crash.Reason.MissingDependency.Generic">部分模组缺少前置,或与当前 Minecraft 版本不匹配。</sys:String>
<sys:String x:Key="Crash.Reason.MissingDependency.WithDetail">部分模组缺少前置,或与当前 Minecraft 版本不匹配:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.MissingMixinBootstrap">缺少 MixinBootstrap,游戏无法继续加载。</sys:String>
<sys:String x:Key="Crash.Reason.ModConfigCrash.Simple">模组 {0} 的配置读取失败。</sys:String>
<sys:String x:Key="Crash.Reason.ModConfigCrash.WithConfig">模组 {0} 的配置文件 {1} 无法读取。</sys:String>
<sys:String x:Key="Crash.Reason.ModInitializationFailed.Multiple">以下模组初始化失败:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.ModInitializationFailed.Single">模组 {0} 初始化失败。</sys:String>
<sys:String x:Key="Crash.Reason.ModLoaderError.Generic">模组加载器可能已经提供了错误信息。</sys:String>
<sys:String x:Key="Crash.Reason.ModLoaderError.WithDetail">模组加载器提供了以下错误信息:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.ModLoaderIncompatible">模组加载器版本与模组不兼容:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.ModMixinFailed.Multiple">以下模组的 Mixin 注入失败:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.ModMixinFailed.None">部分模组的 Mixin 注入失败。</sys:String>
<sys:String x:Key="Crash.Reason.ModMixinFailed.Single">模组 {0} 的 Mixin 注入失败。</sys:String>
<sys:String x:Key="Crash.Reason.ModRequiresJava11">部分已安装模组需要 Java 11 或更高版本。</sys:String>
<sys:String x:Key="Crash.Reason.MultipleForgeInInstanceJson">实例 JSON 中包含多个 Forge 版本参数。</sys:String>
<sys:String x:Key="Crash.Reason.NightConfigBug">Night Config 读取配置时发生错误。</sys:String>
<sys:String x:Key="Crash.Reason.NoAnalyzableFile">游戏出现了问题,但未找到可分析的相关日志文件。</sys:String>
<sys:String x:Key="Crash.Reason.OldForgeNewJavaIncompatible">旧版 Forge 与当前 Java 版本不兼容。</sys:String>
<sys:String x:Key="Crash.Reason.OpenGl1282">光影或资源包可能触发了 OpenGL 1282 错误。</sys:String>
<sys:String x:Key="Crash.Reason.OptiFineForgeIncompatible">OptiFine 与当前 Forge 或 Minecraft 版本可能不兼容。</sys:String>
<sys:String x:Key="Crash.Reason.OptiFineWorldLoadCrash">当前 OptiFine 版本可能导致世界加载失败。</sys:String>
<sys:String x:Key="Crash.Reason.OutOfMemory">Minecraft 内存不足,无法继续运行。</sys:String>
<sys:String x:Key="Crash.Reason.ResourcePackTooLarge">资源包分辨率可能过高,或显卡性能不足。</sys:String>
<sys:String x:Key="Crash.Reason.ShadersModWithOptiFine">同时安装了 Shaders 模组与 OptiFine。</sys:String>
<sys:String x:Key="Crash.Reason.SpecificBlock.Multiple">世界中的某些方块可能导致了游戏出错。</sys:String>
<sys:String x:Key="Crash.Reason.SpecificBlock.Single">方块 {0} 可能导致了游戏出错。</sys:String>
<sys:String x:Key="Crash.Reason.SpecificEntity.Multiple">世界中的某些实体可能导致了游戏出错。</sys:String>
<sys:String x:Key="Crash.Reason.SpecificEntity.Single">实体 {0} 可能导致了游戏出错。</sys:String>
<sys:String x:Key="Crash.Reason.StackKeyword.Multiple">PCL CE 在错误堆栈中找到了可能相关的关键词:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.StackKeyword.Single">PCL CE 在错误堆栈中找到了可能相关的关键词:{0}</sys:String>
<sys:String x:Key="Crash.Reason.SuspectedMod.Multiple">崩溃报告怀疑以下模组导致了游戏出错:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.SuspectedMod.Single">崩溃报告怀疑模组 {0} 导致了游戏出错。</sys:String>
<sys:String x:Key="Crash.Reason.TooManyModsIdLimit">模组数量过多,超出了旧版 Minecraft 的 ID 限制。</sys:String>
<sys:String x:Key="Crash.Reason.Unknown.Auto">PCL CE 无法确定游戏出错的具体原因。</sys:String>
<sys:String x:Key="Crash.Reason.Unknown.Manual">PCL CE 无法确定错误原因。</sys:String>
<sys:String x:Key="Crash.Reason.UnknownFinding">PCL CE 检测到一个没有详细说明的错误原因:{0}</sys:String>
<sys:String x:Key="Crash.Reason.UsingJdk">游戏可能正在使用 JDK,或当前 Java 版本过高。</sys:String>
<sys:String x:Key="Crash.Reason.UsingOpenJ9">当前使用的 OpenJ9 Java 可能与游戏、模组或加载器不兼容。</sys:String>
<sys:String x:Key="Crash.Reason.VeryShortOutput">游戏输出过短,PCL CE 无法获得足够的分析信息。原始输出:&#xA;{0}</sys:String>
<sys:String x:Key="Crash.Reason.X86JavaMemoryLimit.OnX64Os">当前使用的是 32 位 Java,无法分配游戏所需的内存。</sys:String>
<sys:String x:Key="Crash.Reason.X86JavaMemoryLimit.OnX86Os">当前使用的是 32 位操作系统,无法为游戏提供所需的内存。</sys:String>
<!-- Crash.Report -->
<sys:String x:Key="Crash.Report.Export.Success">错误报告已导出!</sys:String>
<sys:String x:Key="Crash.Report.SaveDialog.DefaultFileName">错误报告-{0}.zip</sys:String>
<sys:String x:Key="Crash.Report.SaveDialog.Filter">Minecraft 错误报告(*.zip)|*.zip</sys:String>
<sys:String x:Key="Crash.Report.SaveDialog.Title">选择保存位置</sys:String>
<!-- Crash.Report.Environment -->
<sys:String x:Key="Crash.Report.Environment.Cpu">CPU{0}</sys:String>
<sys:String x:Key="Crash.Report.Environment.EnvironmentSection">- 环境信息 -</sys:String>
<sys:String x:Key="Crash.Report.Environment.Gpu">显卡 {0}{1} ({2} MiB, {3})</sys:String>
<sys:String x:Key="Crash.Report.Environment.InstanceSection">- 实例信息 -</sys:String>
<sys:String x:Key="Crash.Report.Environment.LauncherId">识别码:{0}</sys:String>
<sys:String x:Key="Crash.Report.Environment.LauncherVersion">PCL CE 版本:{0}</sys:String>
<sys:String x:Key="Crash.Report.Environment.Log4j2NoLookups">Log4j2 NoLookups{0}</sys:String>
<sys:String x:Key="Crash.Report.Environment.MemoryAllocation">内存分配 (分配的内存 / 已安装物理内存){0} / {1} GiB ({2} MiB)</sys:String>
<sys:String x:Key="Crash.Report.Environment.MinecraftFolder">MC 文件夹:{0}</sys:String>
<sys:String x:Key="Crash.Report.Environment.OperatingSystem">操作系统:{0}64 位:{1}, ARM64: {2}</sys:String>
<sys:String x:Key="Crash.Report.Environment.ProfileName">档案名称:{0} (验证方式:{1})</sys:String>
<sys:String x:Key="Crash.Report.Environment.ProfileSection">- 档案信息 -</sys:String>
<sys:String x:Key="Crash.Report.Environment.SelectedJava">选定的 Java 虚拟机:{0}</sys:String>
<!-- Crash.Report.JarInJarMod -->
<sys:String x:Key="Crash.Report.JarInJarMod.ModListDirectory">目录:{0}</sys:String>
<sys:String x:Key="Crash.Report.JarInJarMod.DuplicateEntry">{0} [{1}] &lt;-&gt; 内嵌于 {2}{3}</sys:String>
<sys:String x:Key="Crash.Report.JarInJarMod.DuplicateHeuristic">(按模组 ID 匹配,可能包含误报)</sys:String>
<sys:String x:Key="Crash.Report.JarInJarMod.DuplicateDescription">以下模组可能与内嵌副本冲突并导致崩溃:</sys:String>
<sys:String x:Key="Crash.Report.JarInJarMod.DuplicateNone">无</sys:String>
<sys:String x:Key="Crash.Report.JarInJarMod.DuplicateSection">=== 潜在重复模组===</sys:String>
<sys:String x:Key="Crash.Report.JarInJarMod.JarInJarNone">未发现 Jar-in-Jar 信息</sys:String>
<sys:String x:Key="Crash.Report.JarInJarMod.JarInJarSection">=== Jar-in-Jar 信息(仅启用模组)===</sys:String>
<sys:String x:Key="Crash.Report.JarInJarMod.ModListSection">=== 模组列表 ===</sys:String>
<!-- Crash.Suggestion -->
<sys:String x:Key="Crash.Suggestion.DisableTheseMods">尝试逐个禁用这些模组,再观察游戏是否仍会崩溃。</sys:String>
<sys:String x:Key="Crash.Suggestion.DisableThisMod">尝试禁用该模组,再观察游戏是否仍会崩溃。</sys:String>
<sys:String x:Key="Crash.Suggestion.DuplicateMods">每个模组只能安装一次。删除重复的模组后再启动游戏。</sys:String>
<sys:String x:Key="Crash.Suggestion.ExportReport">需要帮助时,请发送错误报告文件,而不是此窗口的照片或截图。</sys:String>
<sys:String x:Key="Crash.Suggestion.ExtractedModFile">删除 mods 文件夹中已被解压的模组文件夹,并放入完整的 .jar 模组文件后再试。</sys:String>
<sys:String x:Key="Crash.Suggestion.FileOrContentValidationFailed">删除游戏及相关模组后重新下载。</sys:String>
<sys:String x:Key="Crash.Suggestion.FollowLoaderInstructions">请根据上述原始错误信息或错误报告中的加载器提示进行处理。</sys:String>
<sys:String x:Key="Crash.Suggestion.GraphicsDriver.Generic">尝试更新或回退显卡驱动;必要时改用更低版本的 Java。</sys:String>
<sys:String x:Key="Crash.Suggestion.GraphicsDriver.IntelOrIntegrated">若设备有独立显卡,请使用独立显卡启动 PCL CE 与 Minecraft;也可以更新或回退显卡驱动。</sys:String>
<sys:String x:Key="Crash.Suggestion.IncompleteForgeInstallation">在实例设置中重置或重新安装 Forge;不要在打包实例时删除 libraries 文件夹。</sys:String>
<sys:String x:Key="Crash.Suggestion.InvalidModFileName">移除或重命名异常的模组文件,使文件名仅包含英文字母、数字、短横线、下划线和小数点。</sys:String>
<sys:String x:Key="Crash.Suggestion.JavaIncompatible">根据 Minecraft 版本和模组要求,在启动设置中选择合适的 Java。</sys:String>
<sys:String x:Key="Crash.Suggestion.JavaTooNew">在启动设置中改用较低版本的 Java 后再试。</sys:String>
<sys:String x:Key="Crash.Suggestion.LauncherOutdated">当前使用的 PCL CE 版本较旧。更新 PCL CE 可能会解决此问题:设置 → 启动器 → 检查更新。</sys:String>
<sys:String x:Key="Crash.Suggestion.MissingMixinBootstrap">检查 MixinBootstrap、模组加载器和相关前置是否已完整安装。</sys:String>
<sys:String x:Key="Crash.Suggestion.ModConfigCrash">尝试删除该模组的配置文件后重新启动;必要时更新或禁用该模组。</sys:String>
<sys:String x:Key="Crash.Suggestion.ModLoaderIncompatible">打开实例设置并调整或重新安装模组加载器版本。</sys:String>
<sys:String x:Key="Crash.Suggestion.ModMixinFailed">逐步禁用模组,以定位与当前环境不兼容或存在问题的模组。</sys:String>
<sys:String x:Key="Crash.Suggestion.ModRequiresJava11">在启动设置中改用 Java 11 或更高版本后再试。</sys:String>
<sys:String x:Key="Crash.Suggestion.MultipleForgeInInstanceJson">在实例设置中重置或重新安装 Forge,不要使用其他启动器修改 Forge 版本。</sys:String>
<sys:String x:Key="Crash.Suggestion.NightConfigBug">尝试删除相关模组配置文件,或安装 Night Config Fixes。</sys:String>
<sys:String x:Key="Crash.Suggestion.OldForgeNewJavaIncompatible">尝试升级 Forge,或改用较低版本的 Java。</sys:String>
<sys:String x:Key="Crash.Suggestion.OpenGl1282">关闭光影,并移除最近添加的资源包后再试。</sys:String>
<sys:String x:Key="Crash.Suggestion.OptiFineForgeIncompatible">检查 OptiFine 与 Forge 的兼容版本,并重新安装匹配的版本。</sys:String>
<sys:String x:Key="Crash.Suggestion.OptiFineWorldLoadCrash">尝试更换 OptiFine 版本,或暂时移除 OptiFine 后再进入世界。</sys:String>
<sys:String x:Key="Crash.Suggestion.OutOfMemory">在启动设置中增加游戏分配内存,并移除配置要求较高的资源包、模组或光影。也可以在启动前关闭其他软件。</sys:String>
<sys:String x:Key="Crash.Suggestion.ResourcePackTooLarge">移除高清资源包,或降低资源包分辨率;也可以更新显卡驱动。</sys:String>
<sys:String x:Key="Crash.Suggestion.ShadersModWithOptiFine">删除 Shaders Mod。OptiFine 已提供光影支持。</sys:String>
<sys:String x:Key="Crash.Suggestion.SpecificBlock.Multiple">创建一个新世界测试游戏是否正常。若正常运行,问题可能来自原世界中的方块。</sys:String>
<sys:String x:Key="Crash.Suggestion.SpecificBlock.Single">创建一个新世界测试游戏是否正常。若正常运行,可使用地图编辑器移除该方块,或禁用提供该方块的模组。</sys:String>
<sys:String x:Key="Crash.Suggestion.SpecificEntity.Multiple">创建一个新世界并生成实体测试游戏是否正常。若正常运行,问题可能来自原世界中的实体。</sys:String>
<sys:String x:Key="Crash.Suggestion.SpecificEntity.Single">创建一个新世界并生成该实体测试游戏是否正常。若正常运行,可使用地图编辑器移除该实体,或禁用提供它的模组。</sys:String>
<sys:String x:Key="Crash.Suggestion.StackKeyword">检查这些关键词对应的模组或库;也可以导出错误报告以获取更多信息。</sys:String>
<sys:String x:Key="Crash.Suggestion.TooManyModsIdLimit">减少模组数量,或为旧版 Minecraft 安装能够扩展 ID 限制的修复模组。</sys:String>
<sys:String x:Key="Crash.Suggestion.UsingJdk">在启动设置中改用常规 JRE 或其他适合该实例的 Java 后再试。</sys:String>
<sys:String x:Key="Crash.Suggestion.UsingOpenJ9">在启动设置中改用 HotSpot Java 后再试。</sys:String>
<sys:String x:Key="Crash.Suggestion.ViewReport">你可以查看错误报告,以了解错误发生的详细过程。</sys:String>
<sys:String x:Key="Crash.Suggestion.X86JavaMemoryLimit.OnX64Os">安装并在启动设置中选择 64 位 Java 后再试。</sys:String>
<sys:String x:Key="Crash.Suggestion.X86JavaMemoryLimit.OnX86Os">改用 64 位操作系统和 64 位 Java 后再试。</sys:String>
<!-- Download -->
<sys:String x:Key="Download.Error.OperationFailed">下载页面操作失败。请查看错误详情。</sys:String>
<!-- Download.Comp -->
<sys:String x:Key="Download.Comp.Error.OperationFailed">资源下载操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Download.Comp.Hint.ModrinthUnreachable">无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,搜索结果可能不全。&#xA;请稍后重试,或使用 VPN 以改善网络环境。</sys:String>
<sys:String x:Key="Download.Comp.Link.ResolveFailed">未能识别或获取该资源链接</sys:String>
<sys:String x:Key="Download.Comp.Search.Hint">搜索资源</sys:String>
<sys:String x:Key="Download.Comp.Sort.Downloads">下载量</sys:String>
<sys:String x:Key="Download.Comp.Sort.Follows">关注量</sys:String>
<sys:String x:Key="Download.Comp.Sort.Latest">最新发布</sys:String>
<sys:String x:Key="Download.Comp.Sort.Relevance">相关性</sys:String>
<sys:String x:Key="Download.Comp.Sort.Updated">最近更新</sys:String>
<!-- Download.Comp.Category -->
<sys:String x:Key="Download.Comp.Category.Adventure">冒险</sys:String>
<sys:String x:Key="Download.Comp.Category.Animated">动态效果</sys:String>
<sys:String x:Key="Download.Comp.Category.Audio">含声音</sys:String>
<sys:String x:Key="Download.Comp.Category.Automation">自动化</sys:String>
<sys:String x:Key="Download.Comp.Category.Biomes">生物群系</sys:String>
<sys:String x:Key="Download.Comp.Category.Cartoon">卡通风</sys:String>
<sys:String x:Key="Download.Comp.Category.ColoredLighting">彩色光照</sys:String>
<sys:String x:Key="Download.Comp.Category.Combat">战斗</sys:String>
<sys:String x:Key="Download.Comp.Category.CoreShaders">核心着色器</sys:String>
<sys:String x:Key="Download.Comp.Category.Creative">创造模式</sys:String>
<sys:String x:Key="Download.Comp.Category.Cursed">鬼畜</sys:String>
<sys:String x:Key="Download.Comp.Category.DataPack.Fantasy">幻想</sys:String>
<sys:String x:Key="Download.Comp.Category.Decoration">装饰</sys:String>
<sys:String x:Key="Download.Comp.Category.Dimensions">维度</sys:String>
<sys:String x:Key="Download.Comp.Category.Display">信息显示</sys:String>
<sys:String x:Key="Download.Comp.Category.Energy">能源</sys:String>
<sys:String x:Key="Download.Comp.Category.Entities">含实体</sys:String>
<sys:String x:Key="Download.Comp.Category.Equipment">装备与工具</sys:String>
<sys:String x:Key="Download.Comp.Category.Fantasy">幻想风</sys:String>
<sys:String x:Key="Download.Comp.Category.Farming">农业</sys:String>
<sys:String x:Key="Download.Comp.Category.Feature">特性</sys:String>
<sys:String x:Key="Download.Comp.Category.Fonts">含字体</sys:String>
<sys:String x:Key="Download.Comp.Category.FoodCooking">食物与烹饪</sys:String>
<sys:String x:Key="Download.Comp.Category.GameMechanics">游戏机制</sys:String>
<sys:String x:Key="Download.Comp.Category.Gui">含 UI</sys:String>
<sys:String x:Key="Download.Comp.Category.Iris">Iris</sys:String>
<sys:String x:Key="Download.Comp.Category.Library">支持库</sys:String>
<sys:String x:Key="Download.Comp.Category.Locale">含语言</sys:String>
<sys:String x:Key="Download.Comp.Category.Magic">魔法</sys:String>
<sys:String x:Key="Download.Comp.Category.Medieval">中世纪</sys:String>
<sys:String x:Key="Download.Comp.Category.Mobs">生物</sys:String>
<sys:String x:Key="Download.Comp.Category.Mod.Mod">模组</sys:String>
<sys:String x:Key="Download.Comp.Category.Mod.ModRelated">模组相关</sys:String>
<sys:String x:Key="Download.Comp.Category.ModSupport">兼容模组</sys:String>
<sys:String x:Key="Download.Comp.Category.Models">含模型</sys:String>
<sys:String x:Key="Download.Comp.Category.Modern">现代风</sys:String>
<sys:String x:Key="Download.Comp.Category.Multiplayer">多人</sys:String>
<sys:String x:Key="Download.Comp.Category.Optifine">OptiFine</sys:String>
<sys:String x:Key="Download.Comp.Category.Optimization">性能优化</sys:String>
<sys:String x:Key="Download.Comp.Category.OresResources">矿物与资源</sys:String>
<sys:String x:Key="Download.Comp.Category.Other">其他</sys:String>
<sys:String x:Key="Download.Comp.Category.PathTracing">路径追踪</sys:String>
<sys:String x:Key="Download.Comp.Category.Pbr">PBR</sys:String>
<sys:String x:Key="Download.Comp.Category.Performance">性能负荷</sys:String>
<sys:String x:Key="Download.Comp.Category.PerformanceHigh">高</sys:String>
<sys:String x:Key="Download.Comp.Category.PerformanceLow">低</sys:String>
<sys:String x:Key="Download.Comp.Category.PerformanceMedium">中</sys:String>
<sys:String x:Key="Download.Comp.Category.PerformancePotato">极低</sys:String>
<sys:String x:Key="Download.Comp.Category.PipesLogistics">管道与物流</sys:String>
<sys:String x:Key="Download.Comp.Category.Realistic">写实风</sys:String>
<sys:String x:Key="Download.Comp.Category.Redstone">红石</sys:String>
<sys:String x:Key="Download.Comp.Category.Reflections">反射</sys:String>
<sys:String x:Key="Download.Comp.Category.Resolution">分辨率</sys:String>
<sys:String x:Key="Download.Comp.Category.SemiRealistic">半写实风</sys:String>
<sys:String x:Key="Download.Comp.Category.Server">服务器</sys:String>
<sys:String x:Key="Download.Comp.Category.Simplistic">简洁</sys:String>
<sys:String x:Key="Download.Comp.Category.Steampunk">蒸汽朋克</sys:String>
<sys:String x:Key="Download.Comp.Category.Storage">仓储</sys:String>
<sys:String x:Key="Download.Comp.Category.Structures">天然结构</sys:String>
<sys:String x:Key="Download.Comp.Category.Style">风格</sys:String>
<sys:String x:Key="Download.Comp.Category.Technology">科技</sys:String>
<sys:String x:Key="Download.Comp.Category.Themed">主题化</sys:String>
<sys:String x:Key="Download.Comp.Category.Transportation">运输</sys:String>
<sys:String x:Key="Download.Comp.Category.Tweaks">改良</sys:String>
<sys:String x:Key="Download.Comp.Category.Utility">实用</sys:String>
<sys:String x:Key="Download.Comp.Category.VanillaAvailable">原版可用</sys:String>
<sys:String x:Key="Download.Comp.Category.VanillaLike">原版风</sys:String>
<sys:String x:Key="Download.Comp.Category.World.Adventure">冒险</sys:String>
<sys:String x:Key="Download.Comp.Category.World.Creative">创造</sys:String>
<sys:String x:Key="Download.Comp.Category.World.ModWorld">模组世界</sys:String>
<sys:String x:Key="Download.Comp.Category.World.Parkour">跑酷</sys:String>
<sys:String x:Key="Download.Comp.Category.World.Puzzle">解谜</sys:String>
<sys:String x:Key="Download.Comp.Category.World.Survival">生存</sys:String>
<sys:String x:Key="Download.Comp.Category.WorldGen">世界元素</sys:String>
<!-- Download.Comp.Category.Modpack -->
<sys:String x:Key="Download.Comp.Category.Modpack.Exploration">探索</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.ExtraLarge">大型整合</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.Ftb">FTB</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.Ftb.ToolTip">由 FTB 官方发布</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.Hardcore">硬核</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.KitchenSink">水槽包</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.MapBased">基于地图</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.MiniGame">小游戏</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.Quests">任务</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.SciFi">科幻</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.Skyblock">空岛</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.SmallLight">轻量整合</sys:String>
<sys:String x:Key="Download.Comp.Category.Modpack.VanillaPlus">原版改良</sys:String>
<!-- Download.Comp.Category.ResourcePack -->
<sys:String x:Key="Download.Comp.Category.ResourcePack.Resolution128x">128x</sys:String>
<sys:String x:Key="Download.Comp.Category.ResourcePack.Resolution16x">16x</sys:String>
<sys:String x:Key="Download.Comp.Category.ResourcePack.Resolution256x">256x</sys:String>
<sys:String x:Key="Download.Comp.Category.ResourcePack.Resolution32x">32x</sys:String>
<sys:String x:Key="Download.Comp.Category.ResourcePack.Resolution48x">48x</sys:String>
<sys:String x:Key="Download.Comp.Category.ResourcePack.Resolution512xOrHigher">512x 或更高</sys:String>
<sys:String x:Key="Download.Comp.Category.ResourcePack.Resolution64x">64x</sys:String>
<sys:String x:Key="Download.Comp.Category.ResourcePack.Resolution8xOrLower">8x 或更低</sys:String>
<!-- Download.Comp.Dependency -->
<sys:String x:Key="Download.Comp.Dependency.Abort.Message">安装已中止。&#xA;&#xA;原因:&#xA;{0}</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Abort.Title">安装已中止</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Install.Cancel">取消安装</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Install.ListItem">- {0}{1}- {2} v{3}</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Install.Message">此模组需要以下必需前置:&#xA;&#xA;{0}</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Install.Title">安装模组前置确认</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Install.WithDependencies">安装模组与必需前置</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Install.WithoutDependencies">仅下载模组本体(不建议)</sys:String>
<sys:String x:Key="Download.Comp.Dependency.ResolveFailed.Continue">继续下载</sys:String>
<sys:String x:Key="Download.Comp.Dependency.ResolveFailed.Message">无法解析模组前置,将仅下载模组本体。</sys:String>
<sys:String x:Key="Download.Comp.Dependency.ResolveFailed.Title">前置解析失败</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Unresolved.ContinueWithoutDependencies">继续下载</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Unresolved.ListItem">- {0} {1}: {2}</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Unresolved.Message">以下必需前置无法解析:&#xA;&#xA;{0}</sys:String>
<sys:String x:Key="Download.Comp.Dependency.Unresolved.Title">无法安装必需前置</sys:String>
<!-- Download.Comp.Detail -->
<sys:String x:Key="Download.Comp.Detail.CleanCache">清理缓存</sys:String>
<sys:String x:Key="Download.Comp.Detail.Clipboard.Detected.Message">检测到剪贴板中包含资源链接,是否前往详情页?</sys:String>
<sys:String x:Key="Download.Comp.Detail.Clipboard.Detected.Title">检测到资源链接</sys:String>
<sys:String x:Key="Download.Comp.Detail.Clipboard.Fetching">正在获取资源信息……</sys:String>
<sys:String x:Key="Download.Comp.Detail.Clipboard.InvalidContent">资源内容无效。</sys:String>
<sys:String x:Key="Download.Comp.Detail.CopyLink">复制链接</sys:String>
<sys:String x:Key="Download.Comp.Detail.CopyName">复制名称</sys:String>
<sys:String x:Key="Download.Comp.Detail.DescriptionNoTranslation">当前资源的简介暂无译文</sys:String>
<sys:String x:Key="Download.Comp.Detail.DescriptionTranslating">正在获取 {0} 的简介译文……</sys:String>
<sys:String x:Key="Download.Comp.Detail.DescriptionTranslationFailed">获取译文失败</sys:String>
<sys:String x:Key="Download.Comp.Detail.DescriptionTranslationResult">原文:{0}&#xA;译文:{1}</sys:String>
<sys:String x:Key="Download.Comp.Detail.DownloadFile">下载文件</sys:String>
<sys:String x:Key="Download.Comp.Detail.DownloadModpackFile">下载整合包文件</sys:String>
<sys:String x:Key="Download.Comp.Detail.DownloadResource">{0}下载:{1}</sys:String>
<sys:String x:Key="Download.Comp.Detail.DownloadWorldFile">下载世界文件</sys:String>
<sys:String x:Key="Download.Comp.Detail.Favorite">收藏</sys:String>
<sys:String x:Key="Download.Comp.Detail.Favorites.Add">已将 {0} 添加到 {1} 中</sys:String>
<sys:String x:Key="Download.Comp.Detail.Favorites.BulkAdd">已将 {0} 个资源添加到 {1} 中!</sys:String>
<sys:String x:Key="Download.Comp.Detail.Favorites.BulkAddWithFailures">已将 {0} 个资源添加到 {1} 中,{2} 个资源已添加!</sys:String>
<sys:String x:Key="Download.Comp.Detail.Favorites.DefaultName">默认</sys:String>
<sys:String x:Key="Download.Comp.Detail.Favorites.FavoriteContextMenu">收藏到 {0}</sys:String>
<sys:String x:Key="Download.Comp.Detail.Favorites.Remove">已将 {0} 从 {1} 中删除</sys:String>
<sys:String x:Key="Download.Comp.Detail.Favorites.UnfavoriteContextMenu">取消收藏 {0}</sys:String>
<sys:String x:Key="Download.Comp.Detail.FindingApplicableInstance">正在查找适合的游戏实例……</sys:String>
<sys:String x:Key="Download.Comp.Detail.InputInstanceName">输入实例名称</sys:String>
<sys:String x:Key="Download.Comp.Detail.InstallWorld">安装世界</sys:String>
<sys:String x:Key="Download.Comp.Detail.InstanceFilter">实例筛选:</sys:String>
<sys:String x:Key="Download.Comp.Detail.KnownButton">我知道了</sys:String>
<sys:String x:Key="Download.Comp.Detail.LoaderFilter">模组加载器筛选:</sys:String>
<sys:String x:Key="Download.Comp.Detail.McMod">MC 百科</sys:String>
<sys:String x:Key="Download.Comp.Detail.ModpackDownload">整合包下载</sys:String>
<sys:String x:Key="Download.Comp.Detail.NoApplicableInstance">当前 MC 文件夹中没有找到适合此资源文件的实例!</sys:String>
<sys:String x:Key="Download.Comp.Detail.OriginalModpack">原始整合包</sys:String>
<sys:String x:Key="Download.Comp.Detail.PrepareModpackInstall">准备安装整合包</sys:String>
<sys:String x:Key="Download.Comp.Detail.ResourceFile.Filter">{0}文件</sys:String>
<sys:String x:Key="Download.Comp.Detail.SelectSaveLocation">选择保存位置</sys:String>
<sys:String x:Key="Download.Comp.Detail.SelectWorldInstallLocation">选择世界安装位置 (saves 文件夹)</sys:String>
<sys:String x:Key="Download.Comp.Detail.SelectedVersion">所选版本:{0}{1}</sys:String>
<sys:String x:Key="Download.Comp.Detail.Task.Cancelled">已取消整合包安装:{0}</sys:String>
<sys:String x:Key="Download.Comp.Detail.Task.Failed">整合包安装失败。&#xA;&#xA;任务:{0}&#xA;&#xA;详细信息:&#xA;{1}</sys:String>
<sys:String x:Key="Download.Comp.Detail.TranslateDescription">翻译简介</sys:String>
<sys:String x:Key="Download.Comp.Detail.Unfavorite">取消收藏</sys:String>
<sys:String x:Key="Download.Comp.Detail.VersionGroup.Old">远古版</sys:String>
<sys:String x:Key="Download.Comp.Detail.VersionGroup.Other">其他</sys:String>
<sys:String x:Key="Download.Comp.Detail.VersionGroup.Snapshot">快照版</sys:String>
<sys:String x:Key="Download.Comp.Detail.VersionRecognitionDelayHint">由于版本信息更新缓慢,可能无法识别刚更新的 MC 版本。几天后即可正常识别。</sys:String>
<sys:String x:Key="Download.Comp.Detail.WorldDownload">世界下载</sys:String>
<sys:String x:Key="Download.Comp.Detail.WorldFile.Filter">世界文件|*.zip</sys:String>
<!-- Download.Comp.Detail.CompItem -->
<sys:String x:Key="Download.Comp.Detail.CompItem.AllVersions">全版本</sys:String>
<sys:String x:Key="Download.Comp.Detail.CompItem.AncientVersion">远古版本</sys:String>
<sys:String x:Key="Download.Comp.Detail.CompItem.Any">任意</sys:String>
<sys:String x:Key="Download.Comp.Detail.CompItem.EditionSuffix"> 版</sys:String>
<sys:String x:Key="Download.Comp.Detail.CompItem.PreviewSuffix"> 预览版</sys:String>
<sys:String x:Key="Download.Comp.Detail.CompItem.SnapshotOnly">仅快照版本</sys:String>
<sys:String x:Key="Download.Comp.Detail.CompItem.Unknown">未知</sys:String>
<sys:String x:Key="Download.Comp.Detail.CompItem.UnknownVersion">未知版本</sys:String>
<!-- Download.Comp.Detail.FileList -->
<sys:String x:Key="Download.Comp.Detail.FileList.DependencyCount">{0} 项前置</sys:String>
<sys:String x:Key="Download.Comp.Detail.FileList.GameVersion">游戏版本 {0}</sys:String>
<sys:String x:Key="Download.Comp.Detail.FileList.OptionalDependencies">可选前置资源</sys:String>
<sys:String x:Key="Download.Comp.Detail.FileList.ReleaseType.Alpha">早期测试版</sys:String>
<sys:String x:Key="Download.Comp.Detail.FileList.ReleaseType.Beta">测试版</sys:String>
<sys:String x:Key="Download.Comp.Detail.FileList.ReleaseType.Release">正式版</sys:String>
<sys:String x:Key="Download.Comp.Detail.FileList.RequiredDependencies">必要前置资源</sys:String>
<sys:String x:Key="Download.Comp.Detail.FileList.Updated">更新于 {0}</sys:String>
<sys:String x:Key="Download.Comp.Detail.FileList.VersionList">版本列表</sys:String>
<!-- Download.Comp.Favorites -->
<sys:String x:Key="Download.Comp.Favorites.Action.Manage">管理</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Action.Unfavorite">取消收藏</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Cancel">取消收藏</sys:String>
<sys:String x:Key="Download.Comp.Favorites.CardTitle">{0} ({1})</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Category.Mod">模组 ({0})</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Deselect">取消选择</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Download">下载所选</sys:String>
<sys:String x:Key="Download.Comp.Favorites.EditNote">修改备注</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Empty.Description">在资源详细信息界面中可以点击收藏按钮进行收藏</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Empty.Title">还没有收藏内容</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Error.OperationFailed">收藏夹操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Download.Comp.Favorites.GetInfoFailed">部分资源在线信息获取失败,点此查看详情</sys:String>
<sys:String x:Key="Download.Comp.Favorites.LoaderName.BatchDownload">批量下载资源({0}</sys:String>
<sys:String x:Key="Download.Comp.Favorites.LoaderName.BatchDownloadSuitable">批量下载合适资源</sys:String>
<sys:String x:Key="Download.Comp.Favorites.LoaderName.QueryInfo">查询资源信息</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Loading">正在加载收藏夹列表</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Manage">管理</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Menu.Delete">删除当前收藏夹</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Menu.Import">导入收藏</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Menu.New">新建收藏夹</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Menu.Rename">重命名收藏夹名称</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Menu.Share">分享当前收藏夹</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Search.Hint">搜索收藏夹内容</sys:String>
<sys:String x:Key="Download.Comp.Favorites.SearchResults.Title">搜索结果 ({0})</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Selection.Placeholder">已选择 8 个文件</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Share">分享所选</sys:String>
<sys:String x:Key="Download.Comp.Favorites.UnknownType">未分类类型</sys:String>
<!-- Download.Comp.Favorites.Dialog -->
<sys:String x:Key="Download.Comp.Favorites.Dialog.BulkDownload.Content">批量下载功能仍旧处于测试状态。&#xA;使用此功能下载模组不会自动下载前置项。&#xA;请在下载前仔细思考自己的需求,并仔细检查自己的选择,避免下载错误导致时间和网络流量的浪费。</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.BulkDownload.Title">确定使用此功能?</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Delete.Confirm">确认删除 {0} 收藏夹?&#xA;此收藏夹有 {1} 个收藏项目&#xA;收藏夹 ID 为 {2}&#xA;此操作不可逆!</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Delete.Title">删除确认</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.EditNote.Title">修改备注</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.GetFailed.Content">由于在线资源被删除或者网络问题等因素导致以下资源未获取成功(以资源的 ID 展示)</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.GetFailed.CopyIds">复制这些 ID</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.GetFailed.Remove">移除这些收藏</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.GetFailed.Title">部分收藏项目获取失败</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Import.CurrentFolder">当前收藏夹</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Import.Hint">例如 ["23333"]</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Import.Input">输入分享的收藏</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Import.New">新收藏夹名称</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Import.NewFolder">新的收藏夹</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Import.NewHint">请输入新收藏夹名称</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Import.Type">你希望将分享的收藏加入到当前收藏夹还是新的收藏夹中?</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.New.Title">新建收藏夹</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.NewPrompt">请输入新收藏夹名称</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.Rename.Title">输入新名称</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.SelectLoader.Title">选择期望的加载器</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Dialog.SelectVersion.Title">选择期望的游戏版本</sys:String>
<!-- Download.Comp.Favorites.Hint -->
<sys:String x:Key="Download.Comp.Favorites.Hint.Deleted">已删除收藏夹</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Hint.LastCollection">您不能删除最后一个收藏夹</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Hint.LoadingVersions">请稍后,正在查询详细版本支持中,这可能需要一段时间……</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Hint.NoResource">不存在指定加载器并且同版本的资源</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Hint.NothingShared">分享了个寂寞啊!</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Hint.Removed">已移除相关收藏</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Hint.SelectLoader">所选模组不支持相同的加载器</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Hint.SelectSaveLocation">已选择 {0} 版本,下面请选择保存位置</sys:String>
<sys:String x:Key="Download.Comp.Favorites.Hint.SelectedCount">已选择 {0} 个收藏项目</sys:String>
<!-- Download.Comp.Filter -->
<sys:String x:Key="Download.Comp.Filter.InstallModpack.ToolTip">在当前选择的 Minecraft 文件夹下安装整合包</sys:String>
<sys:String x:Key="Download.Comp.Filter.Loader">加载器</sys:String>
<sys:String x:Key="Download.Comp.Filter.Loader.Any">任意</sys:String>
<sys:String x:Key="Download.Comp.Filter.Loader.AnyShader">任意加载器</sys:String>
<sys:String x:Key="Download.Comp.Filter.Loader.VanillaAvailable">原版可用</sys:String>
<sys:String x:Key="Download.Comp.Filter.Loader.VanillaAvailable.ToolTip">作为资源包加载,可以直接在原版游戏里使用。</sys:String>
<sys:String x:Key="Download.Comp.Filter.Reset">重置条件</sys:String>
<sys:String x:Key="Download.Comp.Filter.Sort">排序方式</sys:String>
<sys:String x:Key="Download.Comp.Filter.Source">来源</sys:String>
<sys:String x:Key="Download.Comp.Filter.Tag">标签</sys:String>
<sys:String x:Key="Download.Comp.Filter.Version">版本</sys:String>
<sys:String x:Key="Download.Comp.Filter.Version.AllInputAvailable">全部 (也可自行输入)</sys:String>
<sys:String x:Key="Download.Comp.Filter.Version.Any">任意</sys:String>
<!-- Download.Comp.List -->
<sys:String x:Key="Download.Comp.List.NoMatchingResults">没有符合条件的结果</sys:String>
<sys:String x:Key="Download.Comp.List.NoResults">无搜索结果,请尝试搜索英文名称</sys:String>
<sys:String x:Key="Download.Comp.List.NoResultsSimple">没有搜索结果</sys:String>
<sys:String x:Key="Download.Comp.List.Source.CurseForgeUnavailable">无法连接到 CurseForge,所以目前仅显示了来自 Modrinth 的内容,搜索结果可能不全。&#xA;请稍后重试,或使用 VPN 以改善网络环境。</sys:String>
<sys:String x:Key="Download.Comp.List.Source.ModrinthUnavailable">无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,搜索结果可能不全。&#xA;请稍后重试,或使用 VPN 以改善网络环境。</sys:String>
<sys:String x:Key="Download.Comp.List.Source.ResourceFetch">社区资源获取:{0}</sys:String>
<!-- Download.Comp.List.Loading -->
<sys:String x:Key="Download.Comp.List.Loading.DataPack">正在获取 数据包 列表</sys:String>
<sys:String x:Key="Download.Comp.List.Loading.Mod">正在获取模组列表</sys:String>
<sys:String x:Key="Download.Comp.List.Loading.Modpack">正在获取 整合包 列表</sys:String>
<sys:String x:Key="Download.Comp.List.Loading.Plugin">正在获取 插件 列表</sys:String>
<sys:String x:Key="Download.Comp.List.Loading.ResourcePack">正在获取 资源包 列表</sys:String>
<sys:String x:Key="Download.Comp.List.Loading.Schematic">正在获取 投影原理图 列表</sys:String>
<sys:String x:Key="Download.Comp.List.Loading.Shader">正在获取 光影包 列表</sys:String>
<sys:String x:Key="Download.Comp.List.Loading.Unknown">正在获取 未知 列表</sys:String>
<sys:String x:Key="Download.Comp.List.Loading.World">正在获取 世界 列表</sys:String>
<!-- Download.Comp.List.Search -->
<sys:String x:Key="Download.Comp.List.Search.DataPack">搜索 数据包</sys:String>
<sys:String x:Key="Download.Comp.List.Search.Mod">搜索模组</sys:String>
<sys:String x:Key="Download.Comp.List.Search.Modpack">搜索 整合包</sys:String>
<sys:String x:Key="Download.Comp.List.Search.Plugin">搜索 插件</sys:String>
<sys:String x:Key="Download.Comp.List.Search.ResourcePack">搜索 资源包</sys:String>
<sys:String x:Key="Download.Comp.List.Search.Schematic">搜索 投影原理图</sys:String>
<sys:String x:Key="Download.Comp.List.Search.Shader">搜索 光影包</sys:String>
<sys:String x:Key="Download.Comp.List.Search.Unknown">搜索 未知</sys:String>
<sys:String x:Key="Download.Comp.List.Search.World">搜索 世界</sys:String>
<!-- Download.Comp.QuickDownload -->
<sys:String x:Key="Download.Comp.QuickDownload.Btn.ToolTip">快速下载最新版本</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.ChooseInstance.Title">选择要下载到的 Minecraft 实例</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.ChooseMethod.AskInstance">选择一个实例…</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.ChooseMethod.AskPath">选择一个文件夹…</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.ChooseMethod.CurrentInstance">下载到当前选中实例</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.ChooseMethod.Title">选择下载方式</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.Hint.DownloadStarted">已开始下载:{0}</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.Hint.Loading">正在获取资源版本信息,请稍候……</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.Hint.NoCompatibleFile">未找到适用于该实例的版本</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.Hint.NoCompatibleInstance">没有与该资源兼容的 Minecraft 实例</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.Hint.NoFile">未找到可下载的文件</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.Hint.NoInstance">当前未选中任何 Minecraft 实例</sys:String>
<sys:String x:Key="Download.Comp.QuickDownload.Hint.SelectFolder">请选择保存位置</sys:String>
<!-- Download.Comp.Type -->
<sys:String x:Key="Download.Comp.Type.Any">任意</sys:String>
<sys:String x:Key="Download.Comp.Type.DataPack">数据包</sys:String>
<sys:String x:Key="Download.Comp.Type.Mod">模组</sys:String>
<sys:String x:Key="Download.Comp.Type.Modpack">整合包</sys:String>
<sys:String x:Key="Download.Comp.Type.Only">仅 {0}</sys:String>
<sys:String x:Key="Download.Comp.Type.Plugin">插件</sys:String>
<sys:String x:Key="Download.Comp.Type.ResourcePack">资源包</sys:String>
<sys:String x:Key="Download.Comp.Type.Schematic">投影原理图</sys:String>
<sys:String x:Key="Download.Comp.Type.Shader">光影包</sys:String>
<sys:String x:Key="Download.Comp.Type.Unknown">未知</sys:String>
<sys:String x:Key="Download.Comp.Type.World">世界</sys:String>
<!-- Download.Install -->
<sys:String x:Key="Download.Install.Error.OperationFailed">安装版本列表操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Download.Install.Hint.MinecraftBack">点击 Minecraft 项即可返回游戏主版本选择页面!</sys:String>
<sys:String x:Key="Download.Install.InstanceIsolation.Warning.Cancel">取消下载</sys:String>
<sys:String x:Key="Download.Install.InstanceIsolation.Warning.Continue">继续</sys:String>
<sys:String x:Key="Download.Install.InstanceIsolation.Warning.Message">你尚未开启实例隔离,多个 MC 实例会共用同一个模组文件夹。&#xD;因此,游戏可能会因为读取到与当前实例不符的模组而崩溃。&#xD;推荐先在 设置 → 启动选项 → 默认实例隔离 中开启实例隔离!</sys:String>
<sys:String x:Key="Download.Install.InstanceIsolation.Warning.Title">实例隔离提示</sys:String>
<sys:String x:Key="Download.Install.StartDownload">开始下载</sys:String>
<!-- Download.Install.Compat -->
<sys:String x:Key="Download.Install.Compat.CompatForgeSpecificOnly">仅兼容特定版本的 Forge</sys:String>
<sys:String x:Key="Download.Install.Compat.IncompatibleWithLiteLoader">与 LiteLoader 不兼容</sys:String>
<sys:String x:Key="Download.Install.Compat.IncompatibleWithLoader">与 {0} 不兼容</sys:String>
<sys:String x:Key="Download.Install.Compat.IncompatibleWithOptiFine">与 OptiFine 不兼容</sys:String>
<sys:String x:Key="Download.Install.Compat.OptiFabricOriginsRequired">不兼容老版本 Fabric,请手动下载 OptiFabric Origins</sys:String>
<sys:String x:Key="Download.Install.Compat.RequiresFabric">需要安装 Fabric</sys:String>
<sys:String x:Key="Download.Install.Compat.RequiresLegacyFabric">需要安装 LegacyFabric</sys:String>
<sys:String x:Key="Download.Install.Compat.RequiresOptiFine">需要安装 OptiFine</sys:String>
<sys:String x:Key="Download.Install.Compat.RequiresOptiFineAndFabric">需要安装 OptiFine 与 Fabric</sys:String>
<!-- Download.Install.State -->
<sys:String x:Key="Download.Install.State.CanAdd">可以添加</sys:String>
<sys:String x:Key="Download.Install.State.GetFailed">获取失败:{0}</sys:String>
<sys:String x:Key="Download.Install.State.GetVersionListFailed">获取版本列表失败:{0}</sys:String>
<sys:String x:Key="Download.Install.State.Getting">获取中……</sys:String>
<sys:String x:Key="Download.Install.State.Loading">加载中……</sys:String>
<sys:String x:Key="Download.Install.State.NoAvailableVersion">没有可用版本</sys:String>
<sys:String x:Key="Download.Install.State.NoVersion">无可用版本</sys:String>
<sys:String x:Key="Download.Install.State.UnknownUnloaded">未知错误,状态为 Unloaded</sys:String>
<!-- Download.Install.Warning -->
<sys:String x:Key="Download.Install.Warning.FabricApi">如果不安装 Fabric API,大多数模组都会无法使用!</sys:String>
<sys:String x:Key="Download.Install.Warning.LegacyFabricApi">如果不安装 Legacy Fabric API,大多数模组都会无法使用!</sys:String>
<sys:String x:Key="Download.Install.Warning.LegacyOptiFabric">安装结束后,请在模组下载中搜索 LegacyOptiFabric 并下载,否则 OptiFine 会无法使用!</sys:String>
<sys:String x:Key="Download.Install.Warning.ModOptiFine">OptiFine 与一部分模组的兼容性不佳,请谨慎安装。</sys:String>
<sys:String x:Key="Download.Install.Warning.OptiFabric">必须安装 OptiFabric 才能正常使用 OptiFine</sys:String>
<sys:String x:Key="Download.Install.Warning.OptiFabricOld">安装结束后,请在模组下载中搜索 OptiFabric Origins 并下载,否则 OptiFine 会无法使用!</sys:String>
<!-- Download.Left -->
<sys:String x:Key="Download.Left.Cleanroom">Cleanroom</sys:String>
<sys:String x:Key="Download.Left.CommunityResources">社区资源</sys:String>
<sys:String x:Key="Download.Left.DataPack">数据包</sys:String>
<sys:String x:Key="Download.Left.Error.UnknownSubPageType">未知的下载子页面种类:{0}</sys:String>
<sys:String x:Key="Download.Left.Fabric">Fabric</sys:String>
<sys:String x:Key="Download.Left.Favorites">收藏夹</sys:String>
<sys:String x:Key="Download.Left.Forge">Forge</sys:String>
<sys:String x:Key="Download.Left.Hint.Refreshing">正在刷新……</sys:String>
<sys:String x:Key="Download.Left.Installation">安装包</sys:String>
<sys:String x:Key="Download.Left.LabyMod">LabyMod</sys:String>
<sys:String x:Key="Download.Left.LegacyFabric">Legacy Fabric</sys:String>
<sys:String x:Key="Download.Left.LiteLoader">LiteLoader</sys:String>
<sys:String x:Key="Download.Left.Minecraft">Minecraft</sys:String>
<sys:String x:Key="Download.Left.Mod">模组</sys:String>
<sys:String x:Key="Download.Left.Modpack">整合包</sys:String>
<sys:String x:Key="Download.Left.NeoForge">NeoForge</sys:String>
<sys:String x:Key="Download.Left.Optifine">OptiFine</sys:String>
<sys:String x:Key="Download.Left.ResourcePack">资源包</sys:String>
<sys:String x:Key="Download.Left.Shader">光影包</sys:String>
<sys:String x:Key="Download.Left.World">世界</sys:String>
<!-- Download.Source -->
<sys:String x:Key="Download.Source.CleanroomOfficial">Cleanroom 官方源</sys:String>
<sys:String x:Key="Download.Source.FabricOfficial">Fabric 官方源</sys:String>
<sys:String x:Key="Download.Source.ForgeOfficial">Forge 官方源</sys:String>
<sys:String x:Key="Download.Source.LegacyFabricOfficial">LegacyFabric 官方源</sys:String>
<sys:String x:Key="Download.Source.LiteLoaderOfficial">LiteLoader 官方源</sys:String>
<sys:String x:Key="Download.Source.MojangOfficial">Mojang 官方源</sys:String>
<sys:String x:Key="Download.Source.NeoForgeOfficial">NeoForge 官方源</sys:String>
<sys:String x:Key="Download.Source.OptiFineOfficial">OptiFine 官方源</sys:String>
<sys:String x:Key="Download.Source.Tag.Uvmc">UVMC 特供下载</sys:String>
<!-- Download.Version -->
<sys:String x:Key="Download.Version.AllVersions">全部版本 ({0})</sys:String>
<sys:String x:Key="Download.Version.Changelog">更新日志</sys:String>
<sys:String x:Key="Download.Version.Cleanroom.Intro.Description">Cleanroom 是针对 1.12.2 基于 Forge 二次开发的模组加载器,理论上与 99% 的 Forge 模组兼容。</sys:String>
<sys:String x:Key="Download.Version.Cleanroom.Intro.Title">Cleanroom 简介</sys:String>
<sys:String x:Key="Download.Version.Cleanroom.LoadingList">正在获取 Cleanroom 列表</sys:String>
<sys:String x:Key="Download.Version.DownloadServer">下载服务端</sys:String>
<sys:String x:Key="Download.Version.Fabric.Intro.Description">Fabric Loader 是新版 Minecraft 下的轻量化模组加载器,你需要先安装它才能安装各种 Fabric 模组。&#xA;本页面提供 Fabric 安装器下载,在下载后你需要手动打开安装器进行安装。</sys:String>
<sys:String x:Key="Download.Version.Fabric.Intro.Title">Fabric 简介</sys:String>
<sys:String x:Key="Download.Version.Fabric.LoadingList">正在获取 Fabric 列表</sys:String>
<sys:String x:Key="Download.Version.Forge.Intro.Description">Forge 是一个模组加载器,你需要先安装 Forge 才能安装各种 Forge 模组。</sys:String>
<sys:String x:Key="Download.Version.Forge.Intro.Title">Forge 简介</sys:String>
<sys:String x:Key="Download.Version.Forge.LoadingList">正在获取 Forge 列表</sys:String>
<sys:String x:Key="Download.Version.Forge.Type">类型:{0}</sys:String>
<sys:String x:Key="Download.Version.Fresh.Development">最新测试版</sys:String>
<sys:String x:Key="Download.Version.Fresh.Latest">最新</sys:String>
<sys:String x:Key="Download.Version.Fresh.Stable">最新稳定版</sys:String>
<sys:String x:Key="Download.Version.Installer.Fabric.Filter">Fabric 安装器 (*.jar)|*.jar</sys:String>
<sys:String x:Key="Download.Version.Installer.LabyMod.Filter">LabyMod 安装器 (*.jar)|*.jar</sys:String>
<sys:String x:Key="Download.Version.Installer.LegacyFabric.Filter">LegacyFabric 安装器 (*.jar)|*.jar</sys:String>
<sys:String x:Key="Download.Version.LabyMod.Intro.Description">LabyMod 是 Minecraft 下的优化客户端。&#xA;本页面提供 LabyMod 安装器下载,在下载后你需要手动打开安装器进行安装。</sys:String>
<sys:String x:Key="Download.Version.LabyMod.Intro.Title">LabyMod 简介</sys:String>
<sys:String x:Key="Download.Version.LabyMod.LoadingList">正在获取 LabyMod 列表</sys:String>
<sys:String x:Key="Download.Version.Latest.Development">最新预览版,发布于 {0}</sys:String>
<sys:String x:Key="Download.Version.Latest.Release">最新正式版,发布于 {0}</sys:String>
<sys:String x:Key="Download.Version.Latest.Title">最新版本</sys:String>
<sys:String x:Key="Download.Version.LegacyFabric.Intro.Description">Legacy Fabric 是 Fabric 的旧版本移植,你需要先安装它才能安装各种 Legacy Fabric 模组。&#xA;本页面提供 Legacy Fabric 安装器下载,在下载后你需要手动打开安装器进行安装。</sys:String>
<sys:String x:Key="Download.Version.LegacyFabric.Intro.Title">Legacy Fabric 简介</sys:String>
<sys:String x:Key="Download.Version.LegacyFabric.LoadingList">正在获取 Legacy Fabric 列表</sys:String>
<sys:String x:Key="Download.Version.LiteLoader.Intro.Description">与 Forge 类似,LiteLoader 可以用于加载老版本 Minecraft 中的 LiteLoader 模组。</sys:String>
<sys:String x:Key="Download.Version.LiteLoader.Intro.Title">LiteLoader 简介</sys:String>
<sys:String x:Key="Download.Version.LiteLoader.LoadingList">正在获取 LiteLoader 列表</sys:String>
<sys:String x:Key="Download.Version.LoadingList">正在获取版本列表</sys:String>
<sys:String x:Key="Download.Version.NeoForge.Intro.Description">NeoForge 是 Minecraft 1.20.1+ 的模组加载器,你需要先安装它才能安装各种 NeoForge 模组,它也兼容一些 Forge 模组。&#xA;本页面提供 NeoForge 安装器下载,在下载后你需要手动打开安装器进行安装。</sys:String>
<sys:String x:Key="Download.Version.NeoForge.Intro.Title">NeoForge 简介</sys:String>
<sys:String x:Key="Download.Version.NeoForge.LoadingList">正在获取 NeoForge 列表</sys:String>
<sys:String x:Key="Download.Version.OpenWebsite">打开官网</sys:String>
<sys:String x:Key="Download.Version.Optifine.CompatibleForge">兼容 Forge {0}</sys:String>
<sys:String x:Key="Download.Version.Optifine.IncompatibleForge">不兼容 Forge</sys:String>
<sys:String x:Key="Download.Version.Optifine.Intro.Description">OptiFine 又称为高清修复,以允许安装光影、使用高清材质、提高游戏性能,但与模组的兼容性不佳。</sys:String>
<sys:String x:Key="Download.Version.Optifine.Intro.Title">OptiFine 简介</sys:String>
<sys:String x:Key="Download.Version.Optifine.LoadingList">正在获取 OptiFine 列表</sys:String>
<sys:String x:Key="Download.Version.Optifine.Snapshot">快照版本</sys:String>
<sys:String x:Key="Download.Version.ReleaseDate">发布于 {0}</sys:String>
<sys:String x:Key="Download.Version.SaveAs">另存为</sys:String>
<sys:String x:Key="Download.Version.SaveInstaller">保存安装器</sys:String>
<sys:String x:Key="Download.Version.SelectSaveLocation">选择保存位置</sys:String>
<sys:String x:Key="Download.Version.VersionList">版本列表</sys:String>
<sys:String x:Key="Download.Version.VersionListCount">版本列表({0}</sys:String>
<sys:String x:Key="Download.Version.ViewAllVersions">查看全部版本</sys:String>
<!-- Download.Version.Type -->
<sys:String x:Key="Download.Version.Type.AprilFools">愚人节版</sys:String>
<sys:String x:Key="Download.Version.Type.BeforeRelease">远古版</sys:String>
<sys:String x:Key="Download.Version.Type.Development">预览版</sys:String>
<sys:String x:Key="Download.Version.Type.Installer">安装器</sys:String>
<sys:String x:Key="Download.Version.Type.Preview">测试版</sys:String>
<sys:String x:Key="Download.Version.Type.Recommended">推荐版</sys:String>
<sys:String x:Key="Download.Version.Type.Release">正式版</sys:String>
<sys:String x:Key="Download.Version.Type.Snapshot">快照版</sys:String>
<sys:String x:Key="Download.Version.Type.Stable">稳定版</sys:String>
<!-- Event -->
<sys:String x:Key="Event.Download.Confirm">即将从该网址下载文件:&#xA;{0}</sys:String>
<sys:String x:Key="Event.Error.DownloadUrlRequired">EventData 必须为以 http:// 或 https:// 开头的网址。&#xA;PCL CE 不支持其他下载协议。</sys:String>
<sys:String x:Key="Event.Error.ExecutionFailed">事件执行失败({0}, {1}</sys:String>
<sys:String x:Key="Event.Error.FileNotFound">未找到 EventData 指向的本地文件:{0}</sys:String>
<sys:String x:Key="Event.Error.MissingArgs">EventType {0} 需要至少 2 个以 | 分割的参数,例如 {1}</sys:String>
<sys:String x:Key="Event.Error.Title">事件执行失败</sys:String>
<sys:String x:Key="Event.Error.UnknownType">未知的事件类型:{0}&#xA;请检查事件类型填写是否正确,或者 PCL CE 是否为最新版本。</sys:String>
<sys:String x:Key="Event.Error.UrlRequired">EventData 必须为一个网址。&#xA;如果想要启动程序,请将 EventType 改为 OpenFile。</sys:String>
<sys:String x:Key="Event.LaunchGame.SelectVersion">请先选择一个 Minecraft 实例!</sys:String>
<sys:String x:Key="Event.LaunchGame.Starting">正在启动 {0}……</sys:String>
<sys:String x:Key="Event.OpenUrl.Opening">正在开启中,请稍候:{0}</sys:String>
<sys:String x:Key="Event.Refresh.NotSupported">当前页面不支持刷新操作!</sys:String>
<sys:String x:Key="Event.Refresh.Success">已刷新!</sys:String>
<sys:String x:Key="Event.Safety.ConfirmMessage">即将执行:{0}&#xA;请在确认没有安全隐患后再继续。</sys:String>
<sys:String x:Key="Event.Safety.ContinueAlways">继续且今后不再要求确认</sys:String>
<sys:String x:Key="Event.Safety.DangerousSettingBlocked">自定义主页不能写入高危设置:{0}</sys:String>
<sys:String x:Key="Event.Safety.Title">执行确认</sys:String>
<sys:String x:Key="Event.Sanitize.UnknownTypeHint">[{0}] 是无法解析的 EventType,已从主页中移除。</sys:String>
<sys:String x:Key="Event.Sanitize.UnsupportedTypeHint">[{0}] 是不受支持的原版 PCL EventType,已从主页中移除。</sys:String>
<sys:String x:Key="Event.Setting.Written">已写入设置:{0} → {1}</sys:String>
<sys:String x:Key="Event.Variable.Written">已写入变量:{0} → {1}</sys:String>
<!-- Instance -->
<sys:String x:Key="Instance.Error.OperationFailed">实例操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Instance.Install.Action.ModifyLabel">修改</sys:String>
<sys:String x:Key="Instance.Install.Action.StartModify">开始修改</sys:String>
<sys:String x:Key="Instance.Install.Action.StartReset">开始重置</sys:String>
<sys:String x:Key="Instance.Install.Error.OperationFailed">实例安装操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Instance.Install.NoExtraInstall">无附加安装</sys:String>
<sys:String x:Key="Instance.Install.Reset.Message">你正在重置当前实例。&#xA;PCL CE 将会重新联网下载该实例所需的文件,并重新安装模组加载器(如有)。&#xA;此操作不会丢失你的存档、模组、资源包等。</sys:String>
<sys:String x:Key="Instance.Install.Reset.Title">重置此实例</sys:String>
<!-- Instance.Export -->
<sys:String x:Key="Instance.Export.Error.OperationFailed">实例导出操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Instance.Export.ExportTask.Prefix">导出整合包:</sys:String>
<sys:String x:Key="Instance.Export.ModrinthWarning">受 Modrinth 整合包规范限制,导出的整合包中不会包含 OptiFine。</sys:String>
<sys:String x:Key="Instance.Export.MrpackFilter">Modrinth 整合包文件(*.mrpack)|*.mrpack</sys:String>
<sys:String x:Key="Instance.Export.NetCheckAllFailed.Message">联网获取文件信息失败,是否继续导出?&#xA;&#xA;若继续,所有文件都将被直接打包。&#xA;由于二次分发可能违反使用协议,请尽量不要公开发布导出的整合包!</sys:String>
<sys:String x:Key="Instance.Export.NetCheckAllFailed.Title">文件信息获取失败</sys:String>
<sys:String x:Key="Instance.Export.NetCheckPartialFailed.Message">联网获取部分文件信息失败,是否继续导出?&#xA;&#xA;若继续,无法获取信息的文件将被直接打包。&#xA;由于二次分发可能违反使用协议,请尽量不要公开发布导出的整合包!</sys:String>
<sys:String x:Key="Instance.Export.NetCheckPartialFailed.Title">部分文件信息获取失败</sys:String>
<sys:String x:Key="Instance.Export.OptionList.FromConfig">从配置文件中读取</sys:String>
<sys:String x:Key="Instance.Export.OptionListTitle">导出内容列表</sys:String>
<sys:String x:Key="Instance.Export.PackName">整合包名称</sys:String>
<sys:String x:Key="Instance.Export.PackPathInvalid.Title">无法使用配置文件中指定的导出路径</sys:String>
<sys:String x:Key="Instance.Export.PackPathInvalid.WithDetail">无法使用配置文件中指定的导出路径。&#xA;&#xA;指定的路径:{0}&#xA;&#xA;详细信息:&#xA;{1}</sys:String>
<sys:String x:Key="Instance.Export.PackVersion">整合包版本</sys:String>
<sys:String x:Key="Instance.Export.PathError">指定的路径:{0}</sys:String>
<sys:String x:Key="Instance.Export.ReadFailed">读取配置文件失败:{0}</sys:String>
<sys:String x:Key="Instance.Export.ReadSuccess">已读取配置文件:{0}</sys:String>
<sys:String x:Key="Instance.Export.SaveSuccess">已保存配置文件:{0}</sys:String>
<sys:String x:Key="Instance.Export.SelectConfigFile">选择配置文件</sys:String>
<sys:String x:Key="Instance.Export.SelectFileLocation">选择文件位置</sys:String>
<sys:String x:Key="Instance.Export.SelectSaveLocation">选择导出位置</sys:String>
<sys:String x:Key="Instance.Export.StartExport">开始导出</sys:String>
<sys:String x:Key="Instance.Export.Task.CopyContent">复制导出内容</sys:String>
<sys:String x:Key="Instance.Export.Task.CreateArchive">生成压缩包</sys:String>
<sys:String x:Key="Instance.Export.Task.DownloadPclRelease">下载 PCL CE 正式版</sys:String>
<sys:String x:Key="Instance.Export.Task.FetchFileInfo">联网获取文件信息</sys:String>
<sys:String x:Key="Instance.Export.ZipFilter">压缩文件(*.zip)|*.zip</sys:String>
<!-- Instance.Export.Advanced -->
<sys:String x:Key="Instance.Export.Advanced.BundleFiles">打包资源文件,以避免在导入时下载</sys:String>
<sys:String x:Key="Instance.Export.Advanced.BundleFiles.ToolTip">将模组、资源包、光影包的文件直接放入整合包中,这样在导入时就无需联网下载它们。&#xA;建议仅在无法稳定连接 CurseForge 或 Modrinth 时才考虑勾选。</sys:String>
<sys:String x:Key="Instance.Export.Advanced.BundleHint">建议仅在无法稳定连接 CurseForge 或 Modrinth 时才打包资源文件。&#xA;请不要公开发布含资源文件的整合包,这会违反部分模组的使用协议!</sys:String>
<sys:String x:Key="Instance.Export.Advanced.ConfigHint">配置文件中含有更多高级选项,例如精准控制导出的文件、设置整合包存放位置等。&#xA;要修改这些选项,请先点击 "保存配置",在编辑配置文件后再导入。</sys:String>
<sys:String x:Key="Instance.Export.Advanced.ExportConfig">保存配置</sys:String>
<sys:String x:Key="Instance.Export.Advanced.ImportConfig">读取配置</sys:String>
<sys:String x:Key="Instance.Export.Advanced.Modrinth">Modrinth 上传模式</sys:String>
<sys:String x:Key="Instance.Export.Advanced.Modrinth.ToolTip">如果你想要打包上传到 Modrinth,勾选此项会生成完全符合 Modrinth 要求的整合包文件。&#xA;由于 Modrinth 要求,只能从 CurseForge 下载的资源将无法联网下载,会被直接放入整合包中。&#xA;此外,勾选后也不能在整合包中包含 PCL CE 启动器文件。</sys:String>
<sys:String x:Key="Instance.Export.Advanced.Title">高级选项</sys:String>
<!-- Instance.Export.Config -->
<sys:String x:Key="Instance.Export.Config.FileFilter">整合包导出配置(*.txt)|*.txt</sys:String>
<sys:String x:Key="Instance.Export.Config.FileNotFound">未找到配置文件中指定的单个文件:{0}</sys:String>
<sys:String x:Key="Instance.Export.Config.FolderNotFound">未找到配置文件中指定的文件夹:{0}</sys:String>
<sys:String x:Key="Instance.Export.Config.Invalid">配置文件内容无效或为空!</sys:String>
<sys:String x:Key="Instance.Export.Config.ShaderConfigSuffix"> (光影配置文件)</sys:String>
<!-- Instance.Export.Config.Comment -->
<sys:String x:Key="Instance.Export.Config.Comment.BundleFiles">是否将模组、资源包、光影包的文件直接放入整合包中,这样在导入时就无需联网下载它们。</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.BundleFiles2">建议仅在无法稳定连接 CurseForge 或 Modrinth 时才考虑启用。</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.BundleFiles3">二次分发可能违反使用协议,请尽量不要公开发布包含资源文件的整合包!</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.ExcludedFiles">排除的文件</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.ExtraFiles">如果想将额外的文件自动放到压缩包根目录中,可以将它们的路径写在下方。</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.ExtraFiles2">必须是完整路径。每行中,若以 \ 结尾则代表是文件夹,不以 \ 结尾则代表是文件。</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.IncludeLauncher">是否打包正式版 PCL CE,以便没有启动器的玩家安装整合包。</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.IncludeLauncherCustom">是否打包 PCL CE 个性化内容,例如功能隐藏设置、主页、背景音乐和图片等。</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.ModifyRules">修改下方的规则以控制需要导出的内容。</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.Modrinth">如果你想要打包上传到 Modrinth,启用此项会生成完全符合 Modrinth 要求的整合包文件。</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.Modrinth2">由于 Modrinth 要求,只能从 CurseForge 下载的资源将无法联网下载,会被直接放入整合包中。</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.Modrinth3">此选项与 IncludeLauncher、IncludeLauncherCustom、DontCheckHostedAssets 冲突。</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.PackPath">导出的文件的存放位置。</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.PackPath2">若设置了此项,在导出时会直接将文件放到此路径,不会弹窗要求选择。</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.PackPath3">若 IncludeLauncher 为 True,应以 .zip 结尾;若为 False,应以 .mrpack 结尾。</sys:String>
<sys:String x:Key="Instance.Export.Config.Comment.ReverseMatch">以 ! 开头以反选。可以使用 *、?、[] 通配符。靠后的行覆盖靠前的。</sys:String>
<!-- Instance.Export.Option -->
<sys:String x:Key="Instance.Export.Option.DisabledMod">已禁用的模组</sys:String>
<sys:String x:Key="Instance.Export.Option.Emi">EMI 个人信息</sys:String>
<sys:String x:Key="Instance.Export.Option.Emi.Desc">物品收藏夹、默认配方、合成历史记录等</sys:String>
<sys:String x:Key="Instance.Export.Option.Game">游戏本体</sys:String>
<sys:String x:Key="Instance.Export.Option.GameProfile">游戏本体个人信息</sys:String>
<sys:String x:Key="Instance.Export.Option.GameProfile.Desc">命令历史、已保存的快捷栏</sys:String>
<sys:String x:Key="Instance.Export.Option.GameSettings">游戏本体设置</sys:String>
<sys:String x:Key="Instance.Export.Option.GameSettings.Desc">键位、音量、视频设置等</sys:String>
<sys:String x:Key="Instance.Export.Option.ImportantData">整合包重要数据</sys:String>
<sys:String x:Key="Instance.Export.Option.ImportantData.Desc">脚本文件、内置资源包、数据包等</sys:String>
<sys:String x:Key="Instance.Export.Option.Jei">JEI 个人信息</sys:String>
<sys:String x:Key="Instance.Export.Option.Jei.Desc">物品收藏夹等</sys:String>
<sys:String x:Key="Instance.Export.Option.Launcher">PCL CE 启动器程序</sys:String>
<sys:String x:Key="Instance.Export.Option.Launcher.Desc">打包 PCL CE,以便没有启动器的玩家安装整合包</sys:String>
<sys:String x:Key="Instance.Export.Option.LauncherCustom">PCL CE 个性化内容</sys:String>
<sys:String x:Key="Instance.Export.Option.LauncherCustom.Desc">功能隐藏设置、主页、背景音乐和图片等</sys:String>
<sys:String x:Key="Instance.Export.Option.Maps">已绘制的地图</sys:String>
<sys:String x:Key="Instance.Export.Option.Maps.Desc">地图类模组为现有的存档、服务器记录的地图、路标点等</sys:String>
<sys:String x:Key="Instance.Export.Option.Mod">模组</sys:String>
<sys:String x:Key="Instance.Export.Option.Mod.Desc">模组</sys:String>
<sys:String x:Key="Instance.Export.Option.ModSettings">模组设置</sys:String>
<sys:String x:Key="Instance.Export.Option.OptiFine">OptiFine 设置</sys:String>
<sys:String x:Key="Instance.Export.Option.Patchouli">帕秋莉手册个人信息</sys:String>
<sys:String x:Key="Instance.Export.Option.Patchouli.Desc">教程书的已读记录、书签、阅读历史记录等</sys:String>
<sys:String x:Key="Instance.Export.Option.Replay">录像回放</sys:String>
<sys:String x:Key="Instance.Export.Option.Replay.Desc">Replay 模组的录像文件</sys:String>
<sys:String x:Key="Instance.Export.Option.ResourcePacks">资源包</sys:String>
<sys:String x:Key="Instance.Export.Option.ResourcePacks.Desc">纹理包/材质包</sys:String>
<sys:String x:Key="Instance.Export.Option.Saves">单人游戏存档</sys:String>
<sys:String x:Key="Instance.Export.Option.Saves.Desc">世界/地图</sys:String>
<sys:String x:Key="Instance.Export.Option.Screenshots">截图</sys:String>
<sys:String x:Key="Instance.Export.Option.OtherFolders">其他文件夹</sys:String>
<sys:String x:Key="Instance.Export.Option.OtherFolders.Desc">未被上方选项覆盖的文件夹</sys:String>
<sys:String x:Key="Instance.Export.Option.Servers">多人游戏服务器列表</sys:String>
<sys:String x:Key="Instance.Export.Option.ShaderPacks">光影包</sys:String>
<sys:String x:Key="Instance.Export.Option.Structures">导出的结构</sys:String>
<sys:String x:Key="Instance.Export.Option.Structures.Desc">schematics 文件夹</sys:String>
<!-- Instance.Left -->
<sys:String x:Key="Instance.Left.Export">导出</sys:String>
<sys:String x:Key="Instance.Left.Game">游戏本体</sys:String>
<sys:String x:Key="Instance.Left.InitializeSettings.ConfirmMessage">是否要初始化该实例的实例独立设置?该操作不可撤销。</sys:String>
<sys:String x:Key="Instance.Left.InitializeSettings.ConfirmTitle">初始化确认</sys:String>
<sys:String x:Key="Instance.Left.Install">修改</sys:String>
<sys:String x:Key="Instance.Left.Mod">模组</sys:String>
<sys:String x:Key="Instance.Left.Overview">概览</sys:String>
<sys:String x:Key="Instance.Left.Refreshing">刷新中……</sys:String>
<sys:String x:Key="Instance.Left.ResourcePacks">资源包</sys:String>
<sys:String x:Key="Instance.Left.Resources">游戏资源</sys:String>
<sys:String x:Key="Instance.Left.Saves">存档</sys:String>
<sys:String x:Key="Instance.Left.Schematics">投影原理图</sys:String>
<sys:String x:Key="Instance.Left.Screenshots">截图</sys:String>
<sys:String x:Key="Instance.Left.Servers">服务器</sys:String>
<sys:String x:Key="Instance.Left.Settings">设置</sys:String>
<sys:String x:Key="Instance.Left.Shaders">光影包</sys:String>
<!-- Instance.Overall -->
<sys:String x:Key="Instance.Overall.Category.AprilFools">愚人节版本</sys:String>
<sys:String x:Key="Instance.Overall.Category.Hidden">从实例列表中隐藏</sys:String>
<sys:String x:Key="Instance.Overall.Category.Hidden.ToolTip">该实例默认将不会在实例列表中显示。&#xA;在实例列表页面按下 F11,即可查看所有隐藏的实例。</sys:String>
<sys:String x:Key="Instance.Overall.Category.LessUsed">不常用实例</sys:String>
<sys:String x:Key="Instance.Overall.Category.Modable">可安装模组的实例</sys:String>
<sys:String x:Key="Instance.Overall.Category.Regular">常规实例</sys:String>
<sys:String x:Key="Instance.Overall.Category.Title">分类</sys:String>
<sys:String x:Key="Instance.Overall.Delete.ConfirmMessage">你确定要删除实例 {0} 吗?</sys:String>
<sys:String x:Key="Instance.Overall.Delete.ConfirmMessageIsolated">你确定要删除实例 {0} 吗?&#xA;由于该实例开启了实例隔离,删除时该实例对应的存档、资源包、模组等文件也将被一并删除!</sys:String>
<sys:String x:Key="Instance.Overall.Delete.ConfirmMessageIsolatedPermanent">你确定要永久删除实例 {0} 吗?&#xA;由于该实例开启了实例隔离,删除时该实例对应的存档、资源包、模组等文件也将被一并删除!</sys:String>
<sys:String x:Key="Instance.Overall.Delete.ConfirmMessagePermanent">你确定要永久删除实例 {0} 吗?</sys:String>
<sys:String x:Key="Instance.Overall.Delete.ConfirmTitle">实例删除确认</sys:String>
<sys:String x:Key="Instance.Overall.Delete.PermanentSuccess">实例 {0} 已永久删除!</sys:String>
<sys:String x:Key="Instance.Overall.Delete.RecycleBinSuccess">实例 {0} 已删除到回收站!</sys:String>
<sys:String x:Key="Instance.Overall.Description.Default">默认描述</sys:String>
<sys:String x:Key="Instance.Overall.Description.EditMessage">修改实例的描述文本,留空则使用 PCL CE 的默认描述。</sys:String>
<sys:String x:Key="Instance.Overall.Description.EditTitle">更改描述</sys:String>
<sys:String x:Key="Instance.Overall.EditDescription">修改实例描述</sys:String>
<sys:String x:Key="Instance.Overall.Error.OperationFailed">实例概览操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Instance.Overall.Favorite">加入收藏夹</sys:String>
<sys:String x:Key="Instance.Overall.Hide.ConfirmMessage">确认要从实例列表中隐藏该实例吗?隐藏该实例后,它将不再出现于 PCL CE 显示的实例列表中。&#xA;此后,在实例列表页面按下 F11 才可以查看被隐藏的实例。</sys:String>
<sys:String x:Key="Instance.Overall.Hide.ConfirmTitle">隐藏实例提示</sys:String>
<sys:String x:Key="Instance.Overall.Name.EditTitle">重命名实例</sys:String>
<sys:String x:Key="Instance.Overall.Name.RenameSuccess">重命名成功!</sys:String>
<sys:String x:Key="Instance.Overall.Patch.ConfirmMessage">你确定要对 {0} 的核心文件进行修补吗?&#xA;修补游戏核心可能导致游戏崩溃等问题。&#xA;在修补核心后,文件校验会自动关闭。</sys:String>
<sys:String x:Key="Instance.Overall.Patch.ConfirmTitle">修补提示</sys:String>
<sys:String x:Key="Instance.Overall.Patch.Patching">正在修补游戏核心,这可能需要一段时间</sys:String>
<sys:String x:Key="Instance.Overall.Patch.SelectFile.Filter">压缩文件(*.jar;*.zip)|*.jar;*.zip</sys:String>
<sys:String x:Key="Instance.Overall.Patch.SelectFile.Title">选择用于修补核心的文件</sys:String>
<sys:String x:Key="Instance.Overall.Patch.Success">修补游戏核心成功</sys:String>
<sys:String x:Key="Instance.Overall.Personalization.Title">个性化</sys:String>
<sys:String x:Key="Instance.Overall.Rename">修改实例名</sys:String>
<sys:String x:Key="Instance.Overall.Reset.ConfirmMessage">你确定要重置实例 {0} 吗?&#xA;PCL CE 将会尝试重新从互联网获取此实例的资源文件信息,并重新执行自动安装。</sys:String>
<sys:String x:Key="Instance.Overall.Reset.ConfirmTitle">实例重置确认</sys:String>
<sys:String x:Key="Instance.Overall.Reset.NotSupported">该实例暂不支持重置!</sys:String>
<sys:String x:Key="Instance.Overall.Reset.QuiltUnsupported">无法重置:当前启动器已不再支持 Quilt 加载器。</sys:String>
<sys:String x:Key="Instance.Overall.Script.ExportFailed">导出启动脚本失败</sys:String>
<sys:String x:Key="Instance.Overall.Script.Exporting">正在导出启动脚本……</sys:String>
<sys:String x:Key="Instance.Overall.Script.ExportingWarning">正在导出启动脚本……(注意,使用脚本启动可能会导致登录失效!)</sys:String>
<sys:String x:Key="Instance.Overall.Script.FileFilter">批处理文件(*.bat)|*.bat</sys:String>
<sys:String x:Key="Instance.Overall.Script.SelectSaveTitle">选择脚本保存位置</sys:String>
<sys:String x:Key="Instance.Overall.Script.WaitForLaunchTask">请在当前启动任务结束后再试!</sys:String>
<sys:String x:Key="Instance.Overall.Shortcuts.InstanceFolder">实例文件夹</sys:String>
<sys:String x:Key="Instance.Overall.Shortcuts.ModsFolder">模组文件夹</sys:String>
<sys:String x:Key="Instance.Overall.Shortcuts.SavesFolder">存档文件夹</sys:String>
<sys:String x:Key="Instance.Overall.Shortcuts.Title">快捷方式</sys:String>
<sys:String x:Key="Instance.Overall.Unfavorite">从收藏夹中移除</sys:String>
<!-- Instance.Overall.Icon -->
<sys:String x:Key="Instance.Overall.Icon.Anvil">铁砧</sys:String>
<sys:String x:Key="Instance.Overall.Icon.Cleanroom">Cleanroom</sys:String>
<sys:String x:Key="Instance.Overall.Icon.Cobblestone">圆石</sys:String>
<sys:String x:Key="Instance.Overall.Icon.CommandBlock">命令方块</sys:String>
<sys:String x:Key="Instance.Overall.Icon.DirtPath">土径</sys:String>
<sys:String x:Key="Instance.Overall.Icon.Egg">鸡蛋</sys:String>
<sys:String x:Key="Instance.Overall.Icon.Fabric">布料(Fabric</sys:String>
<sys:String x:Key="Instance.Overall.Icon.GoldBlock">金块</sys:String>
<sys:String x:Key="Instance.Overall.Icon.GrassBlock">草方块</sys:String>
<sys:String x:Key="Instance.Overall.Icon.NeoForgeFox">狐狸(NeoForge</sys:String>
<sys:String x:Key="Instance.Overall.Icon.Quilt">方格(Quilt</sys:String>
<sys:String x:Key="Instance.Overall.Icon.RedstoneBlock">红石块</sys:String>
<sys:String x:Key="Instance.Overall.Icon.RedstoneLampOff">红石灯(关)</sys:String>
<sys:String x:Key="Instance.Overall.Icon.RedstoneLampOn">红石灯(开)</sys:String>
<sys:String x:Key="Instance.Overall.Icon.SelectFile.Filter">常用图片文件(*.png;*.jpg;*.gif)|*.png;*.jpg;*.gif</sys:String>
<sys:String x:Key="Instance.Overall.Icon.SelectFile.Title">选择图片</sys:String>
<sys:String x:Key="Instance.Overall.Icon.Title">图标</sys:String>
<!-- Instance.Overall.Info -->
<sys:String x:Key="Instance.Overall.Info.Installed">已安装</sys:String>
<sys:String x:Key="Instance.Overall.Info.LaunchCount.Count">已启动 {0} 次</sys:String>
<sys:String x:Key="Instance.Overall.Info.LaunchCount.Never">从未启动</sys:String>
<sys:String x:Key="Instance.Overall.Info.LaunchCount.Title">启动次数</sys:String>
<sys:String x:Key="Instance.Overall.Info.LoadInstanceInfoTask">获取实例信息</sys:String>
<sys:String x:Key="Instance.Overall.Info.LoadModpackInfoTask">获取可能的整合包信息</sys:String>
<sys:String x:Key="Instance.Overall.Info.Loading">正在获取信息</sys:String>
<sys:String x:Key="Instance.Overall.Info.ModpackVersion">整合包版本</sys:String>
<sys:String x:Key="Instance.Overall.Info.Title">实例信息</sys:String>
<!-- Instance.Overall.Manage -->
<sys:String x:Key="Instance.Overall.Manage.Delete">删除实例</sys:String>
<sys:String x:Key="Instance.Overall.Manage.ExportLaunchScript">导出启动脚本</sys:String>
<sys:String x:Key="Instance.Overall.Manage.PatchCore">修补核心</sys:String>
<sys:String x:Key="Instance.Overall.Manage.Reinstall.ToolTip">重新从互联网获取此实例的安装信息,并执行自动安装。&#xA;你的存档、模组、资源包等信息不会丢失。&#xA;若游戏因 Libraries 等资源缺失无法启动,请尝试此项。</sys:String>
<sys:String x:Key="Instance.Overall.Manage.RepairFiles">补全文件</sys:String>
<sys:String x:Key="Instance.Overall.Manage.RepairFiles.ToolTip">校验实例依赖文件是否完整,并重新下载 Assets 索引与缺失、校验失败的文件。&#xA;若游戏缺失音效,请尝试此项。</sys:String>
<sys:String x:Key="Instance.Overall.Manage.TestGame">测试游戏</sys:String>
<sys:String x:Key="Instance.Overall.Manage.Title">高级管理</sys:String>
<!-- Instance.Overall.Repair -->
<sys:String x:Key="Instance.Overall.Repair.Cancelled.WithTaskName">{0}已取消!</sys:String>
<sys:String x:Key="Instance.Overall.Repair.DisableVerificationHint">请先关闭 [实例设置 → 设置 → 高级启动选项 → 关闭文件校验],然后再尝试补全文件!</sys:String>
<sys:String x:Key="Instance.Overall.Repair.Failed">失败:</sys:String>
<sys:String x:Key="Instance.Overall.Repair.Failed.WithDetail">{0}失败。&#xA;&#xA;详细信息:&#xA;{1}</sys:String>
<sys:String x:Key="Instance.Overall.Repair.Processing">正在处理中,请稍候!</sys:String>
<sys:String x:Key="Instance.Overall.Repair.Success">成功!</sys:String>
<sys:String x:Key="Instance.Overall.Repair.Success.WithTaskName">{0}成功!</sys:String>
<sys:String x:Key="Instance.Overall.Repair.TaskName">文件补全</sys:String>
<!-- Instance.Resource -->
<sys:String x:Key="Instance.Resource.BackToParent">返回上级</sys:String>
<sys:String x:Key="Instance.Resource.CancelSelection">取消选择</sys:String>
<sys:String x:Key="Instance.Resource.CheckMods">检查模组</sys:String>
<sys:String x:Key="Instance.Resource.CheckMods.ToolTip">快速检查模组的版本要求、重复、前置缺失等问题。&#xA;结果仅供参考,由于部分模组标注的信息有误,可能并不准确。</sys:String>
<sys:String x:Key="Instance.Resource.Datapack.DownloadNew">下载新数据包</sys:String>
<sys:String x:Key="Instance.Resource.Datapack.Empty.Description">你可以从已经下载好的文件安装数据包。&#xA;数据包需要放置在存档的 datapacks 文件夹中才能生效。</sys:String>
<sys:String x:Key="Instance.Resource.Datapack.Empty.Title">尚未安装数据包</sys:String>
<sys:String x:Key="Instance.Resource.Datapack.ExportInfo.ToolTip">导出详细的数据包信息</sys:String>
<sys:String x:Key="Instance.Resource.Datapack.Loading">正在加载数据包列表</sys:String>
<sys:String x:Key="Instance.Resource.Delete.Failed">由于文件被占用,删除失败,请尝试关闭正在运行的游戏后再试!</sys:String>
<sys:String x:Key="Instance.Resource.Delete.PermanentMultiple">已彻底删除 {0} 个项目!</sys:String>
<sys:String x:Key="Instance.Resource.Delete.PermanentSingle">已彻底删除 {0}</sys:String>
<sys:String x:Key="Instance.Resource.Delete.RecycleMultiple">已将 {0} 个项目删除到回收站!</sys:String>
<sys:String x:Key="Instance.Resource.Delete.RecycleSingle">已将 {0} 删除到回收站!</sys:String>
<sys:String x:Key="Instance.Resource.Disable">禁用</sys:String>
<sys:String x:Key="Instance.Resource.DownloadNew">下载新资源</sys:String>
<sys:String x:Key="Instance.Resource.Ed.FileConflict">存在文件冲突</sys:String>
<sys:String x:Key="Instance.Resource.Ed.FileConflict.Message">目前同时存在启用和禁用的两个模组文件:&#xA; - {0}&#xA; - {1}&#xA;&#xA;注意,这两个文件的内容并不相同。&#xA;在手动删除或重命名其中一个文件后,才能继续操作。</sys:String>
<sys:String x:Key="Instance.Resource.Ed.ToggleFailed">由于文件被占用,模组的状态切换失败,请尝试关闭正在运行的游戏后再试!</sys:String>
<sys:String x:Key="Instance.Resource.Empty.Description">你可以从已经下载好的文件安装资源。&#xA;如果你已经安装了资源,可能是实例隔离设置有误,请在设置中调整实例隔离选项。</sys:String>
<sys:String x:Key="Instance.Resource.Empty.DescriptionWithDownload">你可以下载新的资源,也可以从已经下载好的文件安装资源。&#xA;如果你已经安装了资源,可能是实例隔离设置有误,请在设置中调整实例隔离选项。</sys:String>
<sys:String x:Key="Instance.Resource.Empty.Title">尚未安装资源</sys:String>
<sys:String x:Key="Instance.Resource.EmptyFolder.Description">你可以从已经下载好的文件安装资源</sys:String>
<sys:String x:Key="Instance.Resource.EmptyFolder.Title">该文件夹为空</sys:String>
<sys:String x:Key="Instance.Resource.Enable">启用</sys:String>
<sys:String x:Key="Instance.Resource.Error.OperationFailed">资源管理操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Instance.Resource.Export.FilesFilter">文本文件(*.txt)|*.txt|CSV 文件(*.csv)|*.csv</sys:String>
<sys:String x:Key="Instance.Resource.Export.Mode.Csv">CSV 格式</sys:String>
<sys:String x:Key="Instance.Resource.Export.Mode.Message">TXT 格式:仅导出当前的资源文件名称信息,通常足够他人获取已安装的资源信息&#xA;CSV 格式:导出详细的资源信息,包括其文件名、工程的 ID、文件内版本信息等详细信息</sys:String>
<sys:String x:Key="Instance.Resource.Export.Mode.Title">选择导出模式</sys:String>
<sys:String x:Key="Instance.Resource.Export.Mode.Txt">TXT 格式</sys:String>
<sys:String x:Key="Instance.Resource.Export.SelectSaveLocation">选择保存位置</sys:String>
<sys:String x:Key="Instance.Resource.ExportInfo">导出信息</sys:String>
<sys:String x:Key="Instance.Resource.ExportInfo.ToolTip">导出详细的资源的版本信息</sys:String>
<sys:String x:Key="Instance.Resource.Favorite">收藏</sys:String>
<sys:String x:Key="Instance.Resource.InstallFromFiles">从文件安装</sys:String>
<sys:String x:Key="Instance.Resource.List.Loading">正在加载资源列表</sys:String>
<sys:String x:Key="Instance.Resource.Loading">正在加载资源列表</sys:String>
<sys:String x:Key="Instance.Resource.Mod.Disabled.GoDownload">转到下载页面</sys:String>
<sys:String x:Key="Instance.Resource.Mod.Disabled.Message">你需要先安装 Forge、Fabric 等模组加载器才能使用模组,请在下载页面安装这些实例。&#xA;如果你已经安装过了模组加载器,那么你很可能选择了错误的实例,请点击实例选择按钮切换实例。</sys:String>
<sys:String x:Key="Instance.Resource.Mod.Disabled.SelectInstance">实例选择</sys:String>
<sys:String x:Key="Instance.Resource.Mod.Disabled.Title">该实例不可使用模组</sys:String>
<sys:String x:Key="Instance.Resource.Schematic.DownloadMod">下载投影模组</sys:String>
<sys:String x:Key="Instance.Resource.Schematic.Unavailable.Message">你可能需要先安装投影模组,如果已经安装过了投影模组请先启动一次游戏。&#xA;也可能是你选择错了实例,请点击实例选择按钮切换实例。</sys:String>
<sys:String x:Key="Instance.Resource.Schematic.Unavailable.Title">该实例不可用投影原理图</sys:String>
<sys:String x:Key="Instance.Resource.Search.Hint">搜索资源:名称 / 描述 / 标签</sys:String>
<sys:String x:Key="Instance.Resource.SelectAll">全选</sys:String>
<sys:String x:Key="Instance.Resource.SelectedCount">已选择 {0} 个文件</sys:String>
<sys:String x:Key="Instance.Resource.ShareSelected">分享所选</sys:String>
<sys:String x:Key="Instance.Resource.Sort.AddTime">加入时间</sys:String>
<sys:String x:Key="Instance.Resource.Sort.FileName">文件名</sys:String>
<sys:String x:Key="Instance.Resource.Sort.FileSize">文件大小</sys:String>
<sys:String x:Key="Instance.Resource.Sort.ResourceName">资源名称</sys:String>
<sys:String x:Key="Instance.Resource.Sort.TagCount">标签数量</sys:String>
<sys:String x:Key="Instance.Resource.Sort.Text">排序:{0}</sys:String>
<!-- Instance.Resource.Filter -->
<sys:String x:Key="Instance.Resource.Filter.All">全部</sys:String>
<sys:String x:Key="Instance.Resource.Filter.AllWithCount">全部 ({0})</sys:String>
<sys:String x:Key="Instance.Resource.Filter.Disabled">已禁用</sys:String>
<sys:String x:Key="Instance.Resource.Filter.DisabledWithCount">禁用 ({0})</sys:String>
<sys:String x:Key="Instance.Resource.Filter.Duplicate">重复</sys:String>
<sys:String x:Key="Instance.Resource.Filter.DuplicateWithCount">重复 ({0})</sys:String>
<sys:String x:Key="Instance.Resource.Filter.Enabled">已启用</sys:String>
<sys:String x:Key="Instance.Resource.Filter.EnabledWithCount">启用 ({0})</sys:String>
<sys:String x:Key="Instance.Resource.Filter.Error">错误</sys:String>
<sys:String x:Key="Instance.Resource.Filter.ErrorWithCount">错误 ({0})</sys:String>
<sys:String x:Key="Instance.Resource.Filter.SearchResult">搜索结果</sys:String>
<sys:String x:Key="Instance.Resource.Filter.Updatable">可更新</sys:String>
<sys:String x:Key="Instance.Resource.Filter.UpdatableWithCount">可更新 ({0})</sys:String>
<!-- Instance.Resource.Install -->
<sys:String x:Key="Instance.Resource.Install.GenericConfirm.Message">是否要将这些文件作为{0}安装到 {1}</sys:String>
<sys:String x:Key="Instance.Resource.Install.GenericConfirm.Title">{0}安装确认</sys:String>
<sys:String x:Key="Instance.Resource.Install.ModConfirm.Message">是否要将这些文件作为模组安装到 {0}</sys:String>
<sys:String x:Key="Instance.Resource.Install.ModConfirm.Title">模组安装确认</sys:String>
<sys:String x:Key="Instance.Resource.Install.OverwriteConfirm.Message">已存在同名文件:{0},是否要覆盖?</sys:String>
<sys:String x:Key="Instance.Resource.Install.OverwriteConfirm.Title">文件覆盖确认</sys:String>
<sys:String x:Key="Instance.Resource.Install.RestoreFromRecycleBin">请先将文件从回收站还原,再尝试安装!</sys:String>
<sys:String x:Key="Instance.Resource.Install.SelectModableInstance">若要安装模组,请先选择一个可以安装模组的实例!</sys:String>
<sys:String x:Key="Instance.Resource.Install.SuccessMultiple">已安装 {0} 个{1}</sys:String>
<sys:String x:Key="Instance.Resource.Install.SuccessSingle">已安装 {0}</sys:String>
<sys:String x:Key="Instance.Resource.Install.UnsupportedFormat">不支持的文件格式:{0}{1}支持的格式:{2}</sys:String>
<!-- Instance.Resource.Install.FileDialog -->
<sys:String x:Key="Instance.Resource.Install.FileDialog.Mod.Filter">模组文件(*.jar;*.litemod;*.disabled;*.old)|*.jar;*.litemod;*.disabled;*.old</sys:String>
<sys:String x:Key="Instance.Resource.Install.FileDialog.Mod.Title">选择要安装的模组</sys:String>
<sys:String x:Key="Instance.Resource.Install.FileDialog.ResourcePack.Filter">资源包文件(*.zip)|*.zip</sys:String>
<sys:String x:Key="Instance.Resource.Install.FileDialog.ResourcePack.Title">选择要安装的资源包</sys:String>
<sys:String x:Key="Instance.Resource.Install.FileDialog.Schematic.Filter">投影原理图文件(*.litematic;*.nbt;*.schematic;*.schem)|*.litematic;*.nbt;*.schematic;*.schem</sys:String>
<sys:String x:Key="Instance.Resource.Install.FileDialog.Schematic.Title">选择要安装的投影原理图</sys:String>
<sys:String x:Key="Instance.Resource.Install.FileDialog.Shader.Filter">光影包文件(*.zip)|*.zip</sys:String>
<sys:String x:Key="Instance.Resource.Install.FileDialog.Shader.Title">选择要安装的光影包</sys:String>
<!-- Instance.Resource.Item -->
<sys:String x:Key="Instance.Resource.Item.ErrorSuffix"> [错误]</sys:String>
<sys:String x:Key="Instance.Resource.Item.FolderTag">文件夹</sys:String>
<sys:String x:Key="Instance.Resource.Item.InfoUnavailable">存在错误,无法获取信息</sys:String>
<sys:String x:Key="Instance.Resource.Item.OpenChangelogFailed">打开更新日志出现错误</sys:String>
<sys:String x:Key="Instance.Resource.Item.Update">更新</sys:String>
<sys:String x:Key="Instance.Resource.Item.UpdateCompare">当前版本:{0}{1}&#xA;最新版本:{2}{3}</sys:String>
<sys:String x:Key="Instance.Resource.Item.UpdateConfirm.Message">是否要更新 {0}</sys:String>
<sys:String x:Key="Instance.Resource.Item.UpdateConfirm.Title">更新确认</sys:String>
<sys:String x:Key="Instance.Resource.Item.UpdateToolTip">点击以更新,右键查看更新日志。</sys:String>
<sys:String x:Key="Instance.Resource.Item.ViewChangelog">查看更新日志</sys:String>
<!-- Instance.Resource.Item.Info -->
<sys:String x:Key="Instance.Resource.Item.Info.Author">作者:{0}</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.ContainsMany">包含 {0} 个文件</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.ContainsOne">包含 1 个文件</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.Dependency">依赖于:</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.DependencyVersion">{0},版本:{1}</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.DetailedError">详细的错误信息:</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.EmptyFolder">空文件夹</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.FailedMessage">无法读取此资源的信息。</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.FailedMessage.WithDetail">无法读取此资源的信息。&#xA;&#xA;详细的错误信息:&#xA;{0}</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.FailedTitle">资源读取失败</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.File">文件:{0}{1}</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.FolderNotFound">文件夹不存在</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.LoadingDetail">正在加载详情....</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.McMod">百科搜索</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.ModId">模组 ID{0}</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.OpenWebsite">打开官网</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.Path">路径:{0}</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.Return">返回</sys:String>
<sys:String x:Key="Instance.Resource.Item.Info.Version">版本:{0}</sys:String>
<!-- Instance.Resource.Item.Schematic -->
<sys:String x:Key="Instance.Resource.Item.Schematic.CreatedTime">创建时间:</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.DataVersion">数据版本:</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.DetailInfo">详细信息:</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.EnclosingDimensions">包围盒尺寸:</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.EnclosingSize">包围盒大小:</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.FileType">文件类型:</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.FileType.Mcedit">MCEdit/WorldEdit Schematic</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.FileType.Nbt">原版结构</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.FileType.Sponge">Sponge Schematic</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.GameVersion">游戏版本:</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.ModifiedTime">修改时间:</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.OriginalName">原始名称:</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.RegionCount">区域数量:</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.Size">大小:</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.SpongeVersion">Sponge 版本:</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.TotalBlocks">总方块数:</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.TotalVolume">总体积:</sys:String>
<sys:String x:Key="Instance.Resource.Item.Schematic.Version">原理图版本:</sys:String>
<!-- Instance.Resource.Update -->
<sys:String x:Key="Instance.Resource.Update">更新</sys:String>
<sys:String x:Key="Instance.Resource.Update.Aborted">资源更新已中止!</sys:String>
<sys:String x:Key="Instance.Resource.Update.Failed">资源更新失败:{0}</sys:String>
<sys:String x:Key="Instance.Resource.Update.SuccessMultiple">已成功更新 {0} 个资源!</sys:String>
<sys:String x:Key="Instance.Resource.Update.SuccessSingle">已成功更新 {0}</sys:String>
<sys:String x:Key="Instance.Resource.Update.Task.DownloadFiles">下载新版资源文件</sys:String>
<sys:String x:Key="Instance.Resource.Update.Task.ReplaceFiles">替换旧版资源文件</sys:String>
<sys:String x:Key="Instance.Resource.Update.Task.Title">资源更新:{0}</sys:String>
<sys:String x:Key="Instance.Resource.Update.WaitingForUpdate">正在更新资源</sys:String>
<sys:String x:Key="Instance.Resource.Update.Warning.Confirm">我已了解风险,继续更新</sys:String>
<sys:String x:Key="Instance.Resource.Update.Warning.Message">新版本模组可能不兼容旧存档或者其他模组,这可能导致游戏崩溃,甚至永久损坏存档!&#xA;如果你在游玩整合包,请千万不要自行更新模组!&#xA;&#xA;在更新前,请先备份存档,并检查模组的更新日志。&#xA;如果更新后出现问题,你也可以在回收站找回更新前的模组。</sys:String>
<sys:String x:Key="Instance.Resource.Update.Warning.Title">模组更新警告</sys:String>
<!-- Instance.Saves -->
<sys:String x:Key="Instance.Saves.CopiedToClipboard">已复制存档文件夹到剪贴板!</sys:String>
<sys:String x:Key="Instance.Saves.CopyFailed">复制失败……</sys:String>
<sys:String x:Key="Instance.Saves.CopyPasteWarning">注意!在粘贴之前进行删除操作会导致存档丢失!</sys:String>
<sys:String x:Key="Instance.Saves.CopySave">复制存档</sys:String>
<sys:String x:Key="Instance.Saves.CreationTime">创建时间:{0},最后修改时间:{1}</sys:String>
<sys:String x:Key="Instance.Saves.DeleteFailed">删除存档失败!</sys:String>
<sys:String x:Key="Instance.Saves.DeletedToRecycleBin">已将存档移至回收站!</sys:String>
<sys:String x:Key="Instance.Saves.Deleting">删除中……</sys:String>
<sys:String x:Key="Instance.Saves.Detail">详情</sys:String>
<sys:String x:Key="Instance.Saves.Details">详情</sys:String>
<sys:String x:Key="Instance.Saves.DuplicateFolder">发现同名文件夹,无法粘贴:{0}</sys:String>
<sys:String x:Key="Instance.Saves.Empty.Message">可以在此处查看当前实例的存档</sys:String>
<sys:String x:Key="Instance.Saves.Empty.Title">暂时没有存档文件</sys:String>
<sys:String x:Key="Instance.Saves.Error.OperationFailed">存档操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Instance.Saves.Folder.DoubleClickHint.Message">现改为双击文件夹进入子文件夹。</sys:String>
<sys:String x:Key="Instance.Saves.Folder.DoubleClickHint.Title">操作提示</sys:String>
<sys:String x:Key="Instance.Saves.Folder.NotFound">文件夹不存在或已被删除</sys:String>
<sys:String x:Key="Instance.Saves.FolderNotFound">存档文件夹不存在!</sys:String>
<sys:String x:Key="Instance.Saves.Left.Datapack">数据包</sys:String>
<sys:String x:Key="Instance.Saves.Left.Info">信息</sys:String>
<sys:String x:Key="Instance.Saves.Left.QuickActions">快捷操作</sys:String>
<sys:String x:Key="Instance.Saves.Left.Refreshing">刷新中……</sys:String>
<sys:String x:Key="Instance.Saves.Left.SwitchFailed">切换分页面失败(ID {0}</sys:String>
<sys:String x:Key="Instance.Saves.Left.UnknownSubPage">未知的实例设置子页面种类:{0}</sys:String>
<sys:String x:Key="Instance.Saves.ListTitle">存档列表</sys:String>
<sys:String x:Key="Instance.Saves.LoadListFailed">载入存档列表失败</sys:String>
<sys:String x:Key="Instance.Saves.OpenFileLocation">打开文件位置</sys:String>
<sys:String x:Key="Instance.Saves.OpenFolder">打开存档文件夹</sys:String>
<sys:String x:Key="Instance.Saves.PasteFile">粘贴剪贴板文件</sys:String>
<sys:String x:Key="Instance.Saves.PasteFolderFailed">粘贴存档文件夹失败</sys:String>
<sys:String x:Key="Instance.Saves.PastedCount">已粘贴 {0} 个文件夹</sys:String>
<sys:String x:Key="Instance.Saves.QuickActions">快捷操作</sys:String>
<sys:String x:Key="Instance.Saves.QuickPlay">快捷启动</sys:String>
<sys:String x:Key="Instance.Saves.RefreshUiFailed">刷新存档UI失败</sys:String>
<sys:String x:Key="Instance.Saves.SaveListTitle">存档列表 ({0})</sys:String>
<sys:String x:Key="Instance.Saves.SearchError">搜索过程中发生异常</sys:String>
<sys:String x:Key="Instance.Saves.SearchHint">搜索存档名称</sys:String>
<sys:String x:Key="Instance.Saves.SearchResultTitle">搜索结果 ({0})</sys:String>
<sys:String x:Key="Instance.Saves.SortBy">排序:{0}</sys:String>
<sys:String x:Key="Instance.Saves.SortCreateTime">创建时间</sys:String>
<sys:String x:Key="Instance.Saves.SortFileName">文件名</sys:String>
<sys:String x:Key="Instance.Saves.SortModifyTime">修改时间</sys:String>
<sys:String x:Key="Instance.Saves.SourceNotFolder">源文件夹不存在或源目标不是文件夹</sys:String>
<sys:String x:Key="Instance.Saves.Status.Refreshing">正在刷新……</sys:String>
<!-- Instance.Saves.Datapack -->
<sys:String x:Key="Instance.Saves.Datapack.Delete.FileOccupied">由于文件被占用,删除失败,请尝试关闭正在运行的游戏后再试!</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Delete.PermanentMultiple">已彻底删除 {0} 个项目!</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Delete.PermanentSingle">已彻底删除 {0}</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Delete.RecycleMultiple">已将 {0} 个项目删除到回收站!</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Delete.RecycleSingle">已将 {0} 删除到回收站!</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Export.Mode.Message">TXT 格式:仅导出当前的数据包文件名称信息&#xA;CSV 格式:导出详细的数据包信息,包括文件名、工程 ID、版本信息等详细信息</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Info.Author">作者:</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Info.DatapackId">数据包 ID</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Info.File">文件:</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Info.ReadFailed">无法读取此数据包的信息。</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Info.ReadFailed.WithDetail">无法读取此数据包的信息。&#xA;&#xA;详细错误:&#xA;{0}</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Info.ReadFailedTitle">数据包读取失败</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Info.Version">版本:</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Install.FileDialog.Filter">数据包文件(*.zip)|*.zip</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Install.FileDialog.Title">选择要安装的数据包</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Install.Message">是否要将这些文件作为数据包安装到当前存档?</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Install.Title">数据包安装确认</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Replace.FileNameConflict">已存在同名文件:{0},请先处理该文件再重试。</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.ToggleWarning">由于文件被占用,数据包的状态切换失败,请尝试关闭正在运行的游戏后再试!</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Update.Task.DownloadFiles">下载新版数据包文件</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Update.Task.ReplaceFiles">替换旧版数据包文件</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Update.Task.Title">数据包更新:{0}</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Update.UnsafeFilesSkipped">已跳过 {0} 个文件名不安全的数据包更新。</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Update.Warning.Confirm">我已了解风险,继续更新</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Update.Warning.Message">新版本数据包可能不兼容旧存档或者其他数据包,这可能导致游戏崩溃或存档损坏!&#xA;&#xA;在更新前,请先备份存档。&#xA;如果更新后出现问题,你也可以在回收站找回更新前的数据包。</sys:String>
<sys:String x:Key="Instance.Saves.Datapack.Update.Warning.Title">数据包更新警告</sys:String>
<!-- Instance.Saves.Info -->
<sys:String x:Key="Instance.Saves.Info.AllowCommands">是否允许作弊</sys:String>
<sys:String x:Key="Instance.Saves.Info.AllowCommands.Allowed">允许</sys:String>
<sys:String x:Key="Instance.Saves.Info.AllowCommands.NotAllowed">不允许</sys:String>
<sys:String x:Key="Instance.Saves.Info.Chunkbase.PreviewVersion">当前存档版本 '{0}' 可能是预览版,不受支持,无法跳转到 Chunkbase</sys:String>
<sys:String x:Key="Instance.Saves.Info.Chunkbase.ToolTip">跳转到 Chunkbase</sys:String>
<sys:String x:Key="Instance.Saves.Info.Chunkbase.UnknownVersion">当前存档版本无法确定,因此无法跳转到 Chunkbase</sys:String>
<sys:String x:Key="Instance.Saves.Info.Details.Title">存档详细信息</sys:String>
<sys:String x:Key="Instance.Saves.Info.Difficulty.Easy">简单</sys:String>
<sys:String x:Key="Instance.Saves.Info.Difficulty.Hard">困难</sys:String>
<sys:String x:Key="Instance.Saves.Info.Difficulty.Normal">普通</sys:String>
<sys:String x:Key="Instance.Saves.Info.Difficulty.Peaceful">和平</sys:String>
<sys:String x:Key="Instance.Saves.Info.DifficultyLocked">是否已锁定难度:{0}</sys:String>
<sys:String x:Key="Instance.Saves.Info.Error.ChunkbaseFailed">跳转到 Chunkbase 失败</sys:String>
<sys:String x:Key="Instance.Saves.Info.Error.ClipboardFailed">复制到剪贴板失败</sys:String>
<sys:String x:Key="Instance.Saves.Info.Error.LoadFailed">获取存档信息失败</sys:String>
<sys:String x:Key="Instance.Saves.Info.GameDifficultyLabel">游戏难度</sys:String>
<sys:String x:Key="Instance.Saves.Info.GameMode">游戏模式</sys:String>
<sys:String x:Key="Instance.Saves.Info.GameMode.Adventure">冒险模式</sys:String>
<sys:String x:Key="Instance.Saves.Info.GameMode.Creative">创造模式</sys:String>
<sys:String x:Key="Instance.Saves.Info.GameMode.Hardcore">极限模式</sys:String>
<sys:String x:Key="Instance.Saves.Info.GameMode.Spectator">旁观模式</sys:String>
<sys:String x:Key="Instance.Saves.Info.GameMode.Survival">生存模式</sys:String>
<sys:String x:Key="Instance.Saves.Info.GetFailed">获取失败</sys:String>
<sys:String x:Key="Instance.Saves.Info.Hardness">困难度</sys:String>
<sys:String x:Key="Instance.Saves.Info.LastPlayed">最后一次游玩</sys:String>
<sys:String x:Key="Instance.Saves.Info.LevelName">存档名称</sys:String>
<sys:String x:Key="Instance.Saves.Info.LockDifficulty">锁定难度</sys:String>
<sys:String x:Key="Instance.Saves.Info.LockDifficulty.ToolTip">锁定当前难度设置,锁定后无法在游戏中更改游戏难度</sys:String>
<sys:String x:Key="Instance.Saves.Info.Modify.BeforeSave">修改设置前请确保该存档未在游戏中打开,否则会导致设置无效</sys:String>
<sys:String x:Key="Instance.Saves.Info.Modify.CheatFailed">作弊设置修改失败</sys:String>
<sys:String x:Key="Instance.Saves.Info.Modify.CheatSuccess">作弊设置修改成功</sys:String>
<sys:String x:Key="Instance.Saves.Info.Modify.DifficultyFailed">难度设置修改失败</sys:String>
<sys:String x:Key="Instance.Saves.Info.Modify.DifficultySuccess">难度设置修改成功</sys:String>
<sys:String x:Key="Instance.Saves.Info.PlayTime">游戏时长</sys:String>
<sys:String x:Key="Instance.Saves.Info.Seed">种子</sys:String>
<sys:String x:Key="Instance.Saves.Info.Settings.Title">存档设置</sys:String>
<sys:String x:Key="Instance.Saves.Info.SpawnPoint">出生点 (X/Y/Z)</sys:String>
<sys:String x:Key="Instance.Saves.Info.Version">存档版本</sys:String>
<sys:String x:Key="Instance.Saves.Info.VersionHint.1_3">1.3 以下的版本无法获取存档版本、游戏难度和是否允许作弊</sys:String>
<sys:String x:Key="Instance.Saves.Info.VersionHint.1_8">1.8 以下的版本无法获取存档版本和游戏难度</sys:String>
<sys:String x:Key="Instance.Saves.Info.VersionHint.1_9">1.9 以下的版本无法获取存档版本</sys:String>
<!-- Instance.Screenshot -->
<sys:String x:Key="Instance.Screenshot.CopiedToClipboard">已复制截图到剪贴板!</sys:String>
<sys:String x:Key="Instance.Screenshot.CopyFailed">截图复制失败!</sys:String>
<sys:String x:Key="Instance.Screenshot.DeleteFailed">删除截图失败!</sys:String>
<sys:String x:Key="Instance.Screenshot.Deleted">已将截图移至回收站!</sys:String>
<sys:String x:Key="Instance.Screenshot.Empty.Message">在游戏内按下截图键(默认为 F2)后,可在此处查看保存的截图</sys:String>
<sys:String x:Key="Instance.Screenshot.Empty.Title">暂时没有截图文件</sys:String>
<sys:String x:Key="Instance.Screenshot.FileNotFound">截图文件不存在!</sys:String>
<sys:String x:Key="Instance.Screenshot.OpenFailed">打开截图失败!</sys:String>
<sys:String x:Key="Instance.Screenshot.OpenFolder">打开截图文件夹</sys:String>
<sys:String x:Key="Instance.Screenshot.QuickActions">快捷操作</sys:String>
<!-- Instance.Server -->
<sys:String x:Key="Instance.Server.AddServer">添加新服务器</sys:String>
<sys:String x:Key="Instance.Server.AddressPrompt">请输入新的服务器地址:</sys:String>
<sys:String x:Key="Instance.Server.DefaultName">Minecraft服务器</sys:String>
<sys:String x:Key="Instance.Server.EditTitle">编辑服务器信息</sys:String>
<sys:String x:Key="Instance.Server.Empty.Message">暂时没有找到服务器,在游戏内添加服务器或点击下方按钮添加新服务器</sys:String>
<sys:String x:Key="Instance.Server.Empty.Title">暂时没有服务器</sys:String>
<sys:String x:Key="Instance.Server.FoundServers">找到服务器列表</sys:String>
<sys:String x:Key="Instance.Server.IndexNotFound">无法找到服务器在列表中的索引</sys:String>
<sys:String x:Key="Instance.Server.NamePrompt">请输入新的服务器名称:</sys:String>
<sys:String x:Key="Instance.Server.NoFrequentRefresh">请勿频繁刷新!</sys:String>
<sys:String x:Key="Instance.Server.NoServersFound">没有找到任何服务器</sys:String>
<sys:String x:Key="Instance.Server.QuickActions">快捷操作</sys:String>
<sys:String x:Key="Instance.Server.ReadDataFailed">无法读取服务器数据文件</sys:String>
<sys:String x:Key="Instance.Server.ReadFileFailed">读取servers.dat文件失败</sys:String>
<sys:String x:Key="Instance.Server.RefreshAll">刷新所有服务器</sys:String>
<sys:String x:Key="Instance.Server.RefreshFailed">刷新服务器列表失败</sys:String>
<sys:String x:Key="Instance.Server.RefreshFailed.WithDetail">刷新服务器列表失败。&#xA;&#xA;详细错误:&#xA;{0}</sys:String>
<sys:String x:Key="Instance.Server.RefreshInfo">刷新服务器信息</sys:String>
<sys:String x:Key="Instance.Server.RefreshingList">正在刷新服务器列表,请稍候...</sys:String>
<sys:String x:Key="Instance.Server.Removed">服务器已移除</sys:String>
<sys:String x:Key="Instance.Server.Updated">服务器信息已更新</sys:String>
<sys:String x:Key="Instance.Server.WriteDataFailed">无法写入服务器数据文件</sys:String>
<!-- Instance.Server.Card -->
<sys:String x:Key="Instance.Server.Card.AddressCopied">已复制服务器地址:{0}</sys:String>
<sys:String x:Key="Instance.Server.Card.Connecting">正在连接</sys:String>
<sys:String x:Key="Instance.Server.Card.ConnectingDots">正在连接...</sys:String>
<sys:String x:Key="Instance.Server.Card.ConnectingTo">正在连接到服务器 {0}...</sys:String>
<sys:String x:Key="Instance.Server.Card.CopyAddress">复制地址</sys:String>
<sys:String x:Key="Instance.Server.Card.CopyAddressFailed">复制服务器地址失败</sys:String>
<sys:String x:Key="Instance.Server.Card.Edit">编辑</sys:String>
<sys:String x:Key="Instance.Server.Card.EditFailed">编辑服务器信息失败:{0}</sys:String>
<sys:String x:Key="Instance.Server.Card.LaunchFailed">启动服务器失败</sys:String>
<sys:String x:Key="Instance.Server.Card.LaunchFailedMsg">启动服务器失败:{0}</sys:String>
<sys:String x:Key="Instance.Server.Card.Offline">离线</sys:String>
<sys:String x:Key="Instance.Server.Card.RefreshingStatus">正在刷新服务器 {0} 的状态...</sys:String>
<sys:String x:Key="Instance.Server.Card.Remove">移除</sys:String>
<sys:String x:Key="Instance.Server.Card.RemoveConfirmMessage">你确定要移除服务器 {0} 吗?&#xA;'{1}' 将从您的列表中移除,包括游戏内列表,且无法恢复。</sys:String>
<sys:String x:Key="Instance.Server.Card.RemoveConfirmTitle">移除服务器确认</sys:String>
<sys:String x:Key="Instance.Server.Card.ServerOffline">服务器离线</sys:String>
<!-- Instance.Setup -->
<sys:String x:Key="Instance.Setup.Error.OperationFailed">实例设置操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Instance.Setup.FollowGlobal">跟随全局设置</sys:String>
<sys:String x:Key="Instance.Setup.Hint">这些设置只对该游戏实例生效,不影响其他实例。</sys:String>
<sys:String x:Key="Instance.Setup.Initialize.Success">已初始化实例独立设置!</sys:String>
<sys:String x:Key="Instance.Setup.Memory.Used">已使用内存</sys:String>
<sys:String x:Key="Instance.Setup.OpenGlobalSettings">全局设置</sys:String>
<!-- Instance.Setup.Advanced -->
<sys:String x:Key="Instance.Setup.Advanced.Classpath">Classpath 头部附加</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.Classpath.ToolTip">文本框中的内容将被作为 Classpath 添加到该实例的启动参数中的 Classpath 头部。&#xA;需要使用绝对路径。若有多个,请以半角 (英文) 分号分隔。</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.DisableFileVerification">关闭文件校验</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.DisableFileVerification.ToolTip">完全不更改 Assets;不校验 Libraries、第三方登录库与实例主 Jar 文件是否被修改。&#xA;如果你没有修改相关文件,请勿勾选此项。</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.GameArgs.ToolTip">文本框中的内容将会被直接拼合在启动参数的末尾。&#xA;例如,输入 --demo 则会以试玩模式启动游戏。&#xA;支持 Minecraft 实例 JSON 中的字符串替换标记,例如 ${library_directory}。&#xA;&#xA;若留空,则跟随全局设置的值。</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.IgnoreJavaCompatibility">忽略 Java 兼容性警告</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.IgnoreJavaCompatibility.ToolTip">如果手动选择了与当前实例不兼容的 Java,则自动跳过兼容性警告弹窗,强制使用手动选择的 Java。</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.JvmArgs.ToolTip">启动 Minecraft 时使用的额外 JVM 参数,在没有确定把握的情况下请不要尝试修改。&#xA;支持 Minecraft 实例 JSON 中的字符串替换标记,例如 ${library_directory}。&#xA;&#xA;若留空,则跟随全局设置的值。</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.PreLaunchCommand.ToolTip">该项不会覆盖全局设置:启动时会先执行全局设置的命令,再执行实例设置的命令。&#xA;&#xA;在 MC 启动前执行特定命令或程序,语法与 Windows 的命令提示符一致。&#xA;&#xA;可以使用以下替换标记实现相对路径(路径均以 \ 结尾):&#xA; · {path}PCL CE 的 exe 文件所在的文件夹&#xA; · {minecraft}.minecraft 文件夹&#xA; · {verpath}:实例文件夹(.minecraft\versions\实例名\&#xA; · {verindie}:开启实例隔离时等同版本文件夹,未开启时等同 .minecraft 文件夹&#xA; · {java}:游戏运行时的 Java 文件夹&#xA;&#xA;除此之外,也支持以下替换标记:&#xA; · {user}:玩家名字&#xA; · {login}:玩家的登录方式&#xA; · {uuid}:玩家的 UUID&#xA; · {name}:游戏实例名&#xA; · {date}、{time}:当前的系统时间&#xA; · {version}:游戏对应的原版版本号&#xA;&#xA;例如:&#xA; · "{verpath}test.exe" :运行实例文件夹下的 test.exe 程序&#xA; · "{java}java.exe" -jar "{verpath}test.jar" :用 Java 运行实例文件夹下的 test.jar&#xA; · notepad "{verindie}options.txt" :使用记事本打开该实例的设置文件&#xA;&#xA;涉及路径的操作最好都打上双引号,以避免路径中的空格导致运行失败。&#xA;执行命令时,命令行所在的目录是当前的 .minecraft 文件夹。</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.UseDebugLog4j">使用调试级别 log4j 配置文件</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.UseDebugLog4j.Message">本选项会修改游戏日志级别修改为最低,大量日志输出会消耗大量磁盘空间并可能影响游戏性能。这也可能带来一定安全风险。如果你不知道你在做什么,不要修改此选项!&#xA;你确定要继续修改吗?</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.UseGlobalProxy">跟随启动器网络代理设置</sys:String>
<sys:String x:Key="Instance.Setup.Advanced.UseGlobalProxy.ToolTip">使用 [设置-其他-网络-HTTP 代理] 中填写的代理来启动游戏。&#xA;错误的全局配置将会导致无法登录!</sys:String>
<!-- Instance.Setup.Options -->
<sys:String x:Key="Instance.Setup.Options.CustomInfo.ToolTip">该信息会显示在游戏主界面的左下角,与 F3 调试页面的左上角。&#xA;若留空,则跟随全局设置的值。</sys:String>
<sys:String x:Key="Instance.Setup.Options.InstanceIsolation">实例隔离</sys:String>
<sys:String x:Key="Instance.Setup.Options.InstanceIsolation.Disabled.ToolTip">与其余关闭隔离的实例共享存档、模组等文件。&#xA;若存在多个安装了模组的实例,可能会由于模组冲突而导致崩溃。</sys:String>
<sys:String x:Key="Instance.Setup.Options.InstanceIsolation.Enabled">开启</sys:String>
<sys:String x:Key="Instance.Setup.Options.InstanceIsolation.Enabled.ToolTip">与其他实例的存档、模组等文件相互独立,互不干涉。&#xA;这会使你无法跨实例共享存档,但可以规避模组冲突问题。</sys:String>
<sys:String x:Key="Instance.Setup.Options.InstanceIsolation.Message">调整实例隔离后,你可能得把游戏存档、模组等文件手动迁移到新的游戏文件夹中。&#xA;如果修改后发现存档消失,把这项设置改回来就能恢复。&#xA;如果你不会迁移存档,不建议修改这项设置!</sys:String>
<sys:String x:Key="Instance.Setup.Options.WindowTitle.Default">默认窗口标题</sys:String>
<sys:String x:Key="Instance.Setup.Options.WindowTitle.Default.ToolTip">游戏窗口标题是否保留默认。</sys:String>
<sys:String x:Key="Instance.Setup.Options.WindowTitle.ToolTip">自定义游戏窗口的标题,若留空则跟随全局设置的值。&#xA;支持以下替换标记:&#xA; · {user}:玩家名字&#xA; · {login}:玩家的登录方式&#xA; · {name}:游戏实例名&#xA; · {date}、{time}:当前的系统时间&#xA; · {version}:游戏对应的原版版本号&#xA;注意:如果你的实例安装了 Modern UI 模组,那么这里的设置将会失效</sys:String>
<!-- Instance.Setup.Options.Java -->
<sys:String x:Key="Instance.Setup.Options.Java">游戏 Java</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.AutoSelect">自动选择合适的 Java</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.Details.ToolTip">路径: {0}&#xA;版本: {1}&#xA;来源: {2}</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.InvalidPathToolTip">无效路径: {0}&#xA;点击此项重新选择有效 Java</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.LoadFailed">列表加载失败,请重试</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.Loading">加载中……</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.NoRuntime">未检测到可用的 Java 运行时</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.NoRuntime.ToolTip">请在设置中手动指定 Java 路径,或点击“扫描”按钮重新检测</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.PathOutOfRange">超出路径允许范围,请选择启动器文件夹或其子文件夹下的文件</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.RelativePathToolTip">相对路径: {0}&#xA;解析路径: {1}</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.SelectRelative">选择启动器目录下的 Java</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.SelectRelative.Invalid">选择启动器目录下的 Java(当前路径无效)</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.SelectRelative.WithJava">启动器目录下的 Java | {0}</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.SelectRelativeToolTip">将选择相对于实例目录的 Java 路径</sys:String>
<sys:String x:Key="Instance.Setup.Options.Java.ToolTip">若将 Java 放在游戏文件夹或 PCL CE 文件夹,在进行自动选择时 PCL CE 会尽量优先选择它</sys:String>
<!-- Instance.Setup.Server -->
<sys:String x:Key="Instance.Setup.Server.AuthServer">目标验证服务器</sys:String>
<sys:String x:Key="Instance.Setup.Server.AuthServer.AutoFormatted">已自动格式化验证服务器地址!</sys:String>
<sys:String x:Key="Instance.Setup.Server.AuthServer.Hint">Yggdrasil API 认证服务器的完整地址</sys:String>
<sys:String x:Key="Instance.Setup.Server.AutoJoin">自动进入服务器</sys:String>
<sys:String x:Key="Instance.Setup.Server.AutoJoin.ToolTip">在打开 Minecraft 后自动进入某服务器。&#xA;用英文冒号间隔 IP 与端口,例如 233.233.233.233:12345。</sys:String>
<sys:String x:Key="Instance.Setup.Server.CreateProfile">新建档案</sys:String>
<sys:String x:Key="Instance.Setup.Server.HttpWarning">使用 HTTP 协议进行登录操作可能面临登录密码在未加密的情况下被明文传输的安全风险。&#xA;如果没有特殊需求,请始终考虑使用 HTTPS 协议!</sys:String>
<sys:String x:Key="Instance.Setup.Server.LittleSkin.Name">LittleSkin 登录</sys:String>
<sys:String x:Key="Instance.Setup.Server.LittleSkin.Override.Continue">继续</sys:String>
<sys:String x:Key="Instance.Setup.Server.LittleSkin.Override.Message">即将把第三方登录设置覆盖为 LittleSkin 登录。&#xA;除非你是服主,或者服主要求你这样做,否则请不要继续。&#xA;&#xA;是否确实需要覆盖当前设置?</sys:String>
<sys:String x:Key="Instance.Setup.Server.LittleSkin.Override.Title">设置覆盖确认</sys:String>
<sys:String x:Key="Instance.Setup.Server.LockLoginMethod">锁定验证方式</sys:String>
<sys:String x:Key="Instance.Setup.Server.LockLoginMethod.Message">你正在选择锁定此实例的验证方式。锁定之后,将无法再更改此实例的验证方式要求,启动此实例将必须使用指定的验证方式。&#xA;此功能可能会帮助一些服主吧。&#xA;是否继续?</sys:String>
<sys:String x:Key="Instance.Setup.Server.LockLoginMethod.Title">锁定验证方式确认</sys:String>
<sys:String x:Key="Instance.Setup.Server.LoginLockHint">当前实例的验证方式已锁定,无法再次更改。请使用要求的验证方式启动游戏。</sys:String>
<sys:String x:Key="Instance.Setup.Server.LoginMethod.AuthOnly">仅第三方验证(Authlib-Injector</sys:String>
<sys:String x:Key="Instance.Setup.Server.LoginMethod.MicrosoftOnly">仅正版验证</sys:String>
<sys:String x:Key="Instance.Setup.Server.LoginMethod.MicrosoftOrAuth">正版验证与第三方验证(Authlib-Injector</sys:String>
<sys:String x:Key="Instance.Setup.Server.LoginMethod.Unlimited">无限制</sys:String>
<sys:String x:Key="Instance.Setup.Server.Name">服务器名称</sys:String>
<sys:String x:Key="Instance.Setup.Server.Name.Hint">显示在登录页面的服务器名称</sys:String>
<sys:String x:Key="Instance.Setup.Server.Register">注册链接</sys:String>
<sys:String x:Key="Instance.Setup.Server.Register.Hint">注册账号的网页地址,如 https://xxxxx.xxx/auth/register</sys:String>
<sys:String x:Key="Instance.Setup.Server.RestrictLogin">限制验证方式</sys:String>
<sys:String x:Key="Instance.Setup.Server.SetLittleSkin">设置为 LittleSkin</sys:String>
<sys:String x:Key="Instance.Setup.Server.SslWarning">未启用 SSL 证书验证可能导致登录密码面临被第三方截获的安全风险。&#xA;如果没有特殊需求,请始终考虑启用 SSL 证书验证!</sys:String>
<sys:String x:Key="Instance.Setup.Server.Title">服务器</sys:String>
<!-- Launch -->
<sys:String x:Key="Launch.Error.ExportScriptTitle">导出启动脚本失败</sys:String>
<sys:String x:Key="Launch.Error.OperationFailed">启动页操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Launch.Error.Title">启动失败</sys:String>
<sys:String x:Key="Launch.Right.CommunityHint.HidePrompt">若要永久隐藏此提示,请输入正确的 PCL CE 开发组织名称。</sys:String>
<sys:String x:Key="Launch.Right.CommunityHint.InputTitle">输入 PCL CE 开发组织名称</sys:String>
<sys:String x:Key="Launch.Right.CommunityHint.Message">你正在使用 PCL CE(PCL 社区版)!此版本为独立开发和维护,与原版维护路线不同,体验有所出入。&#xA;&#xA;如果你是意外下载到了 PCL CE ,我们十分建议您下载原版 PCL 长期使用,此发行版本对新手用户体验可能不友好。&#xA;此外, PCL CE 的问题请向 PCL CE 的仓库提交 Issue,不要向官方仓库反馈 PCL CE 的问题哦!</sys:String>
<sys:String x:Key="Launch.Right.CommunityHint.Title">PCL CE 提示</sys:String>
<sys:String x:Key="Launch.Right.CommunityHint.WrongInput">不太对哦……</sys:String>
<sys:String x:Key="Launch.Right.Log.Title">启动日志</sys:String>
<!-- Launch.Account -->
<sys:String x:Key="Launch.Account.Back">« 返回</sys:String>
<sys:String x:Key="Launch.Account.Error.SwitchPage">切换登录分页失败({0}</sys:String>
<sys:String x:Key="Launch.Account.Login">登录</sys:String>
<sys:String x:Key="Launch.Account.LoginCancelled">已取消登录!</sys:String>
<sys:String x:Key="Launch.Account.Type.Microsoft">正版验证</sys:String>
<sys:String x:Key="Launch.Account.Type.Offline">离线验证</sys:String>
<sys:String x:Key="Launch.Account.Type.ThirdParty">第三方验证</sys:String>
<!-- Launch.Account.Auth -->
<sys:String x:Key="Launch.Account.Auth.Back">返回</sys:String>
<sys:String x:Key="Launch.Account.Auth.Cancelled">已取消登录!</sys:String>
<sys:String x:Key="Launch.Account.Auth.ChangeProfile">更换角色</sys:String>
<sys:String x:Key="Launch.Account.Auth.Email">邮箱</sys:String>
<sys:String x:Key="Launch.Account.Auth.EmptyFields">验证服务器、用户名与密码均不能为空!</sys:String>
<sys:String x:Key="Launch.Account.Auth.ForgotPassword">找回密码</sys:String>
<sys:String x:Key="Launch.Account.Auth.InvalidServer">输入的验证服务器地址无效</sys:String>
<sys:String x:Key="Launch.Account.Auth.Login">登录</sys:String>
<sys:String x:Key="Launch.Account.Auth.LoginFailed">第三方登录尝试失败</sys:String>
<sys:String x:Key="Launch.Account.Auth.LoginFailed.WithDetail">第三方登录尝试失败。&#xA;&#xA;原始错误信息:&#xA;{0}</sys:String>
<sys:String x:Key="Launch.Account.Auth.Logout">退出登录</sys:String>
<sys:String x:Key="Launch.Account.Auth.Password">密码</sys:String>
<sys:String x:Key="Launch.Account.Auth.Preset.LittleSkin">预设 - LittleSkin</sys:String>
<sys:String x:Key="Launch.Account.Auth.Register">注册账号</sys:String>
<sys:String x:Key="Launch.Account.Auth.Server">服务器</sys:String>
<sys:String x:Key="Launch.Account.Auth.ServerLabel">验证服务器: {0}</sys:String>
<!-- Launch.Account.LoginDialog -->
<sys:String x:Key="Launch.Account.LoginDialog.CopyCode">复制代码</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.Error.Init">正版验证弹窗初始化失败</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.Error.Load">正版验证弹窗加载失败</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.MicrosoftInstructions">登录网页将自动开启,请在网页中输入授权码 {0}(将自动复制)。&#xA;&#xA;如果网络环境不佳,网页可能一直加载不出来,届时请使用 VPN 并重试。&#xA;你也可以用其他设备打开 {1} 并输入上述授权码。</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.MicrosoftInstructions.WithAutoFill">登录网页将自动开启,授权码将自动填充。&#xA;&#xA;如果网络环境不佳,网页可能一直加载不出来,届时请使用 VPN 并重试。&#xA;如果没有自动填充,请在页面内粘贴此授权码 {0}(将自动复制)&#xA;你也可以用其他设备打开 {1} 并输入授权码。</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.MinecraftLogin">登录 Minecraft</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.PollingFailed">正版验证轮询失败</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.ReopenWebpage">重新打开网页</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.Success">网页登录成功!</sys:String>
<sys:String x:Key="Launch.Account.LoginDialog.Title">测试标题文本</sys:String>
<!-- Launch.Account.Microsoft -->
<sys:String x:Key="Launch.Account.Microsoft.Error.OperationFailed">Microsoft 登录操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Launch.Account.Microsoft.Error.Unknown">未知错误!</sys:String>
<sys:String x:Key="Launch.Account.Microsoft.LoginFailed.Message">正版登录验证失败,请考虑在 [设置 → 其他] 中关闭 [在正版登录时验证 SSL 证书],然后再试。&#xA;&#xA;原始错误信息:</sys:String>
<sys:String x:Key="Launch.Account.Microsoft.LoginFailed.Title">正版登录尝试失败</sys:String>
<sys:String x:Key="Launch.Account.Microsoft.LoginFailed.WithDetail">正版登录验证失败。&#xA;&#xA;原始错误信息:&#xA;{0}</sys:String>
<sys:String x:Key="Launch.Account.Microsoft.Purchase">» 购买正版</sys:String>
<sys:String x:Key="Launch.Account.Microsoft.Start">开始正版验证</sys:String>
<sys:String x:Key="Launch.Account.Microsoft.Website">» 前往官网</sys:String>
<!-- Launch.Account.Offline -->
<sys:String x:Key="Launch.Account.Offline.Back">返回</sys:String>
<sys:String x:Key="Launch.Account.Offline.Create">创建</sys:String>
<sys:String x:Key="Launch.Account.Offline.InvalidPlayerId.Message">你输入的玩家 ID 不符合标准(3 - 16 位,只可以包含英文字母、数字与下划线),可能导致部分版本的游戏无法启动或发生错误。&#xA;强烈建议使用规范的玩家 ID!&#xA;如果你坚持,仍然可以继续创建档案。</sys:String>
<sys:String x:Key="Launch.Account.Offline.InvalidPlayerId.Title">玩家 ID 不符合规范</sys:String>
<sys:String x:Key="Launch.Account.Offline.InvalidUuid">UUID 不符合要求:{0}</sys:String>
<sys:String x:Key="Launch.Account.Offline.PlayerId">玩家 ID</sys:String>
<sys:String x:Key="Launch.Account.Offline.PlayerId.ToolTip">3 - 16 位,只可以包含英文字母、数字与下划线</sys:String>
<sys:String x:Key="Launch.Account.Offline.Uuid.Custom.Input.ToolTip">应为 32 位 16 进制字符串,不含连字符</sys:String>
<sys:String x:Key="Launch.Account.Offline.Uuid.Custom.ToolTip">适用于高级用户,如果你不知道这是什么,请不要选择此项</sys:String>
<sys:String x:Key="Launch.Account.Offline.Uuid.Legacy">旧版</sys:String>
<sys:String x:Key="Launch.Account.Offline.Uuid.Legacy.ToolTip">使用旧版 PCL CE 的 UUID 生成规则&#xA;若从旧版 PCL CE 升级,且单人存档的部分信息丢失,可尝试此项</sys:String>
<sys:String x:Key="Launch.Account.Offline.Uuid.Standard">行业规范</sys:String>
<sys:String x:Key="Launch.Account.Offline.Uuid.Standard.ToolTip">[推荐] 生成的 UUID 与 HMCL、BakaXL 和 Bukkit 等其他软件一致</sys:String>
<sys:String x:Key="Launch.Account.Offline.UuidStandard">UUID 标准</sys:String>
<!-- Launch.Account.Profile -->
<sys:String x:Key="Launch.Account.Profile.ChangeAuthServerName">更改验证服务器名称</sys:String>
<sys:String x:Key="Launch.Account.Profile.ChangeUuid">更改 UUID</sys:String>
<sys:String x:Key="Launch.Account.Profile.CopyUuid">复制 UUID</sys:String>
<sys:String x:Key="Launch.Account.Profile.Create">新建档案</sys:String>
<sys:String x:Key="Launch.Account.Profile.Create.SelectAuthType.Title">新建档案 - 选择验证类型</sys:String>
<sys:String x:Key="Launch.Account.Profile.CreateAndSelectHint">新建并选择一个档案以启动游戏</sys:String>
<sys:String x:Key="Launch.Account.Profile.RequireMicrosoftHint">需要先进行一次正版验证才能使用其他验证方式</sys:String>
<sys:String x:Key="Launch.Account.Profile.Created">档案新建成功!</sys:String>
<sys:String x:Key="Launch.Account.Profile.Delete">删除档案</sys:String>
<sys:String x:Key="Launch.Account.Profile.DeleteConfirm.Message">你正在选择删除此档案,该操作无法撤销。&#xA;确定继续?</sys:String>
<sys:String x:Key="Launch.Account.Profile.DeleteConfirm.Title">删除档案确认</sys:String>
<sys:String x:Key="Launch.Account.Profile.Deleted">档案删除成功!</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditServerName.Message">请输入新的验证服务器名称</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditServerName.Title">修改验证服务器名称</sys:String>
<sys:String x:Key="Launch.Account.Profile.Error.ChangeId">修改档案 ID 失败</sys:String>
<sys:String x:Key="Launch.Account.Profile.Error.ChangeSkin">更改皮肤失败</sys:String>
<sys:String x:Key="Launch.Account.Profile.Error.Corrupted">档案数据读取失败,文件可能意外损坏。已对档案文件进行备份重置。</sys:String>
<sys:String x:Key="Launch.Account.Profile.Error.Read">读取档案列表失败</sys:String>
<sys:String x:Key="Launch.Account.Profile.Error.Write">写入档案列表失败</sys:String>
<sys:String x:Key="Launch.Account.Profile.ImportExport">导入 / 导出</sys:String>
<sys:String x:Key="Launch.Account.Profile.Saved">档案信息已保存!</sys:String>
<sys:String x:Key="Launch.Account.Profile.SelectHint">选择一个档案以启动游戏</sys:String>
<sys:String x:Key="Launch.Account.Profile.Uuid.ChangeTitle">更改档案 {0} 的 UUID</sys:String>
<sys:String x:Key="Launch.Account.Profile.Uuid.Hint">32 位,不含连字符</sys:String>
<sys:String x:Key="Launch.Account.Profile.Uuid.InvalidChars">UUID 只应该包括英文字母和数字!</sys:String>
<sys:String x:Key="Launch.Account.Profile.Uuid.Legacy">原版 PCL UUID(若单人存档的部分信息丢失,可尝试此项)</sys:String>
<sys:String x:Key="Launch.Account.Profile.Uuid.SelectType.Title">新建档案 - 选择 UUID 类型</sys:String>
<sys:String x:Key="Launch.Account.Profile.Uuid.Standard">行业规范 UUID(推荐)</sys:String>
<sys:String x:Key="Launch.Account.Profile.Validation.EmptyUsername">玩家名不能为空!</sys:String>
<sys:String x:Key="Launch.Account.Profile.Validation.QuoteInUsername">玩家名不能包含英文引号!</sys:String>
<sys:String x:Key="Launch.Account.Profile.Validation.UnknownAuthType">未知的验证方式</sys:String>
<sys:String x:Key="Launch.Account.Profile.Validation.UsernameTooLong">自 1.20.3 起,玩家名至多只能包含 16 个字符!</sys:String>
<!-- Launch.Account.Profile.EditPlayerId -->
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Confirm.Message">注意:玩家 ID 只能每 30 天更改一次,请务必谨慎考虑!</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Confirm.Title">确认修改</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Cooldown">首次更改 ID 后,必须等待 30 天后才能再次修改 ID,你可以前往官网查询具体时间。</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Duplicate">此 ID 已被使用,请换一个 ID。</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Empty">欲设置的玩家名称为空</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Failed.Title">ID 修改失败</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Hint">3 - 16 个字符,只可以包含大小写字母、数字、下划线</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.MicrosoftWarning">玩家 ID 只能每 30 天更改一次名称,请谨慎考虑!</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.NotAllowed">此 ID 包含了除大小写字母、数字、下划线以外的不合法字符。</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Success">玩家 ID 修改成功,当前 ID 为:{0}</sys:String>
<sys:String x:Key="Launch.Account.Profile.EditPlayerId.Title">输入新的玩家 ID</sys:String>
<!-- Launch.Account.ProfileSkin -->
<sys:String x:Key="Launch.Account.ProfileSkin.CapeUnsupported">当前档案不支持修改披风!</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.ChangeCape">修改披风</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.ChangeSkin">修改皮肤</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.EditPassword">修改密码</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.EditProfile">修改信息</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.EditUsername">修改玩家名</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.PasswordUnsupported">当前档案不支持修改密码!</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.RefreshSkin">刷新头像</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.SaveSkin">保存皮肤</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.SkinUnsupported">当前档案不支持修改皮肤!</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.SkinsAndCapes">皮肤与披风</sys:String>
<sys:String x:Key="Launch.Account.ProfileSkin.SwitchProfile">选择档案</sys:String>
<!-- Launch.Home -->
<sys:String x:Key="Launch.Home.Button.Download">下载游戏</sys:String>
<sys:String x:Key="Launch.Home.Button.Launch">启动游戏</sys:String>
<sys:String x:Key="Launch.Home.Button.Loading">正在加载</sys:String>
<sys:String x:Key="Launch.Home.Instance.InstallingCannotLaunch">当前实例正在安装,无法启动!</sys:String>
<sys:String x:Key="Launch.Home.Instance.InstallingCannotSetup">当前实例正在安装,暂无法进行实例设置!</sys:String>
<sys:String x:Key="Launch.Home.Instance.Loading">正在加载中,请稍候</sys:String>
<sys:String x:Key="Launch.Home.Instance.NotFound">未找到可用的游戏实例</sys:String>
<sys:String x:Key="Launch.Home.InstanceSettings">实例设置</sys:String>
<sys:String x:Key="Launch.Home.SelectInstance">实例选择</sys:String>
<sys:String x:Key="Launch.Home.VersionList.Loading">正在加载版本列表,请稍候</sys:String>
<!-- Launch.Homepage -->
<sys:String x:Key="Launch.Homepage.Error.Download">下载主页失败({0}</sys:String>
<sys:String x:Key="Launch.Homepage.Error.ExternalFile">读取外部文件失败:{0}</sys:String>
<sys:String x:Key="Launch.Homepage.LoadFailed.Message">主页内容编写有误,请根据下列错误信息进行检查:&#xA;{0}</sys:String>
<sys:String x:Key="Launch.Homepage.LoadFailed.Retry">重试</sys:String>
<sys:String x:Key="Launch.Homepage.LoadFailed.Title">加载主页界面失败</sys:String>
<sys:String x:Key="Launch.Homepage.Loading">正在加载主页……</sys:String>
<sys:String x:Key="Launch.Homepage.News.Loading">加载中……</sys:String>
<sys:String x:Key="Launch.Homepage.News.ViewDetail">查看详情</sys:String>
<sys:String x:Key="Launch.Homepage.Preset.EchoCave.Removed">回声洞 因为只有空壳因此已被移除,请前往设置选择其他预设主页</sys:String>
<sys:String x:Key="Launch.Homepage.SlowWarning">主页加载过于缓慢(花费了 {0} 秒),请向主页作者反馈此问题,或暂时停止使用该主页</sys:String>
<sys:String x:Key="Launch.Homepage.Task.Download">下载主页</sys:String>
<!-- Launch.Skin -->
<sys:String x:Key="Launch.Skin.ChangeCape">更换披风</sys:String>
<sys:String x:Key="Launch.Skin.ChangeSuccess">更改皮肤成功!</sys:String>
<sys:String x:Key="Launch.Skin.Error.AuthGet">获取 Authlib-Injector 皮肤失败({0}</sys:String>
<sys:String x:Key="Launch.Skin.Error.AuthlibRateLimited">获取 Authlib-Injector 皮肤失败。请求过于频繁,请 5 分钟后再试。</sys:String>
<sys:String x:Key="Launch.Skin.Error.MsGet">获取微软正版皮肤失败({0}</sys:String>
<sys:String x:Key="Launch.Skin.Error.MsRateLimited">获取正版皮肤太过频繁,请 5 分钟后再试({0}</sys:String>
<sys:String x:Key="Launch.Skin.Fetching">皮肤正在获取中,请稍候!</sys:String>
<sys:String x:Key="Launch.Skin.File.Error">皮肤文件存在错误</sys:String>
<sys:String x:Key="Launch.Skin.FileDialog.Filter">皮肤文件(*.png;*.jpg;*.webp)|*.png;*.jpg;*.webp</sys:String>
<sys:String x:Key="Launch.Skin.FileDialog.Title">选择皮肤文件</sys:String>
<sys:String x:Key="Launch.Skin.FileTooLarge">皮肤文件大小需小于 24 KiB,而所选文件大小为 {0} KiB</sys:String>
<sys:String x:Key="Launch.Skin.InvalidSize">皮肤图片大小应为 64x32 像素或 64x64 像素!</sys:String>
<sys:String x:Key="Launch.Skin.Load.Error.Avatar">载入头像失败:{0}</sys:String>
<sys:String x:Key="Launch.Skin.Load.Error.Corrupted">皮肤文件已损坏:{0}</sys:String>
<sys:String x:Key="Launch.Skin.Model.Alex">Alex 模型</sys:String>
<sys:String x:Key="Launch.Skin.Model.SelectMessage">此皮肤为 Steve 模型(粗手臂)还是 Alex 模型(细手臂)?</sys:String>
<sys:String x:Key="Launch.Skin.Model.SelectTitle">选择皮肤种类</sys:String>
<sys:String x:Key="Launch.Skin.Model.Steve">Steve 模型</sys:String>
<sys:String x:Key="Launch.Skin.Model.UnknownHint">请在皮肤下载页面确认皮肤种类后再使用此皮肤!</sys:String>
<sys:String x:Key="Launch.Skin.Refresh.Busy">有正在获取中的皮肤,请稍后再试!</sys:String>
<sys:String x:Key="Launch.Skin.Refresh.Error">刷新皮肤缓存失败</sys:String>
<sys:String x:Key="Launch.Skin.RefreshSuccess">已刷新头像!</sys:String>
<sys:String x:Key="Launch.Skin.Refreshing">正在刷新头像……</sys:String>
<sys:String x:Key="Launch.Skin.Save.Error">保存皮肤失败</sys:String>
<sys:String x:Key="Launch.Skin.SaveAs">另存为</sys:String>
<sys:String x:Key="Launch.Skin.SaveDialog.Filter">皮肤图片文件(*.png)|*.png</sys:String>
<sys:String x:Key="Launch.Skin.SaveDialog.Title">选取保存皮肤的位置</sys:String>
<sys:String x:Key="Launch.Skin.SaveSuccess">皮肤保存成功!</sys:String>
<!-- Launch.Skin.Cape -->
<sys:String x:Key="Launch.Skin.Cape.ChangeFailed">更改披风失败</sys:String>
<sys:String x:Key="Launch.Skin.Cape.ChangeFailedWithReason">更改披风失败:{0}</sys:String>
<sys:String x:Key="Launch.Skin.Cape.ChangeSuccess">更改披风成功!等待一段时间后将会生效……</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Changing">正在更改披风中,请稍候!</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Error.List">获取玩家皮肤列表失败</sys:String>
<sys:String x:Key="Launch.Skin.Cape.FetchingList">正在获取披风列表,请稍候……</sys:String>
<sys:String x:Key="Launch.Skin.Cape.LoginFailed">登录失败,无法更改披风!</sys:String>
<sys:String x:Key="Launch.Skin.Cape.None">无披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.SelectTitle">选择披风</sys:String>
<!-- Launch.Skin.Cape.Name -->
<sys:String x:Key="Launch.Skin.Cape.Name.Builder">建造者披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.CherryBlossom">樱花披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Cobalt">Cobalt 披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Common">普通披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Copper">铜披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Crafter">工匠披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.FifteenthAnniversary">15 周年纪念披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Followers">追随者披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Founders">创始人披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Home">家园披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.MapMaker">Realms 地图制作者披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Mcc">MCC 15 周年披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Menace">入侵披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Migrator">迁移者披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Minecon2011">Minecon 2011 参与者披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Minecon2012">Minecon 2012 参与者披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Minecon2013">Minecon 2013 参与者披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Minecon2015">Minecon 2015 参与者披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Minecon2016">Minecon 2016 参与者披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.MinecraftExperience">村民救援披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Moderator">Mojira 管理员披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.MojangOffice">Mojang 办公室披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.MoonlightTrail">Moonlight Trail 披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Pan">薄煎饼披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.PurpleHeart">紫色心形披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Translator">Crowdin 翻译者披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.TranslatorChinese">Crowdin 中文翻译者披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Vanilla">原版披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.Yearn">渴望披风</sys:String>
<sys:String x:Key="Launch.Skin.Cape.Name.ZombieHorse">僵尸马披风</sys:String>
<!-- Launch.Skin.Change -->
<sys:String x:Key="Launch.Skin.Change.Busy">正在更改皮肤中,请稍候!</sys:String>
<sys:String x:Key="Launch.Skin.Change.Error.MsRefresh">更改正版皮肤后刷新皮肤失败</sys:String>
<sys:String x:Key="Launch.Skin.Change.FailedWithDetail">更改皮肤失败。&#xA;&#xA;详细信息:&#xA;{0}</sys:String>
<sys:String x:Key="Launch.Skin.Change.LoginFailed">登录失败,无法更改皮肤!</sys:String>
<sys:String x:Key="Launch.Skin.Change.Reauthenticating">正在登录,将在登录完成后继续更改皮肤……</sys:String>
<sys:String x:Key="Launch.Skin.Change.Starting">正在更改皮肤……</sys:String>
<sys:String x:Key="Launch.Skin.Change.Timeout.WithDetail">更改皮肤失败:与 Mojang 皮肤服务器的连接超时,请检查网络连接。&#xA;&#xA;详细信息:&#xA;{0}</sys:String>
<sys:String x:Key="Launch.Skin.Change.ToolTip">点击更换皮肤(右键查看更多选项)</sys:String>
<!-- Launch.Status -->
<sys:String x:Key="Launch.Status.Completed">已完成</sys:String>
<sys:String x:Key="Launch.Status.CurrentStep">当前步骤</sys:String>
<sys:String x:Key="Launch.Status.DownloadLibs">下载支持</sys:String>
<sys:String x:Key="Launch.Status.DownloadSpeed">下载速度</sys:String>
<sys:String x:Key="Launch.Status.LaunchProgress">启动进度</sys:String>
<sys:String x:Key="Launch.Status.LoginMethod">验证方式</sys:String>
<sys:String x:Key="Launch.Status.MicrosoftLogin">正版验证</sys:String>
<sys:String x:Key="Launch.Status.Title.ExportingScript">正在导出启动脚本</sys:String>
<sys:String x:Key="Launch.Status.Title.Launched">已启动游戏</sys:String>
<sys:String x:Key="Launch.Status.Title.Launching">正在启动游戏</sys:String>
<sys:String x:Key="Launch.Status.Trivia">你知道吗</sys:String>
<sys:String x:Key="Launch.Status.Trivia.Placeholder">这是一段测试用的小提示文本,它应该足够长以让它有两行。</sys:String>
<!-- Link -->
<sys:String x:Key="Link.Connection.Local">本机</sys:String>
<sys:String x:Key="Link.Connection.P2P">P2P</sys:String>
<sys:String x:Key="Link.Connection.Relay">中继</sys:String>
<sys:String x:Key="Link.Connection.Unknown">未知</sys:String>
<sys:String x:Key="Link.Natayark.TokenFetchEmpty">获取 AccessToken 与 RefreshToken 失败,返回内容为空</sys:String>
<sys:String x:Key="Link.Natayark.TokenParseFailed">获取 AccessToken 与 RefreshToken 失败,解析返回内容失败</sys:String>
<sys:String x:Key="Link.Natayark.UserInfoFetchEmpty">获取 Natayark 用户信息失败,返回内容为空</sys:String>
<sys:String x:Key="Link.Natayark.UserInfoParseFailed">获取 Natayark 用户信息失败,解析返回内容失败</sys:String>
<sys:String x:Key="Link.Quality.Good">优秀</sys:String>
<sys:String x:Key="Link.Quality.GoodDescription">当前网络环境不会影响联机体验&#xA;该网络环境适合作为大厅创建者</sys:String>
<sys:String x:Key="Link.Quality.Normal">一般</sys:String>
<sys:String x:Key="Link.Quality.NormalDescription">当前网络环境可能会影响您的联机体验</sys:String>
<sys:String x:Key="Link.Quality.Poor">较差</sys:String>
<sys:String x:Key="Link.Quality.PoorDescription">部分路由器和防火墙设置可能会影响您的联机体验</sys:String>
<!-- Link.Lobby -->
<sys:String x:Key="Link.Lobby.CreateLobbyFailed">创建大厅失败,请检查日志或向开发者反馈。</sys:String>
<sys:String x:Key="Link.Lobby.CreateRoomFailed">在创建房间的时候遇到了问题,请查看日志并将此问题反馈给开发者!</sys:String>
<sys:String x:Key="Link.Lobby.InitFailed">大厅服务初始化失败,请检查网络连接。</sys:String>
<sys:String x:Key="Link.Lobby.InvalidCodeFormat">大厅编号格式不正确,请检查后再试!</sys:String>
<sys:String x:Key="Link.Lobby.JoinFailed">加入大厅失败,可能是大厅不存在或已被解散</sys:String>
<sys:String x:Key="Link.Lobby.LoginRequired">请先登录 Natayark ID 再使用大厅!</sys:String>
<sys:String x:Key="Link.Lobby.MotdDesc"> - {0}</sys:String>
<sys:String x:Key="Link.Lobby.MotdFormat">PCL CE 大厅{0}</sys:String>
<sys:String x:Key="Link.Lobby.UnsupportedType">不支持的大厅类型: {0}</sys:String>
<sys:String x:Key="Link.Lobby.WorldNameFormat">{0} / {1} ({2})</sys:String>
<!-- Link.Mod -->
<sys:String x:Key="Link.Mod.AccountBanned">你的 Natayark Network 账号状态异常,可能已被封禁!</sys:String>
<sys:String x:Key="Link.Mod.DownloadComplete">联机组件下载完成!</sys:String>
<sys:String x:Key="Link.Mod.DownloadEasyTierFailed">下载 EasyTier 依赖文件失败,请检查网络连接</sys:String>
<sys:String x:Key="Link.Mod.DownloadingDeps">正在下载联机依赖组件,请稍后...</sys:String>
<sys:String x:Key="Link.Mod.DownloadingEasyTier">正在下载 EasyTier,请稍后...</sys:String>
<sys:String x:Key="Link.Mod.EasyTierNotReady">EasyTier 尚未下载完成,请等待其下载完成后再试!</sys:String>
<sys:String x:Key="Link.Mod.InvalidPlayerId">MC 玩家 ID 不可包含分隔符 (|) </sys:String>
<sys:String x:Key="Link.Mod.LobbyUnavailable">大厅功能暂不可用,请稍后再试</sys:String>
<sys:String x:Key="Link.Mod.LoginFirst">请先前往联机设置并登录至 Natayark Network 再进行联机!</sys:String>
<sys:String x:Key="Link.Mod.NaidFetchFailed">尝试获取 Natayark ID 信息失败</sys:String>
<sys:String x:Key="Link.Mod.ReLoginRequired">请重新登录 Natayark Network 账号再试!</sys:String>
<sys:String x:Key="Link.Mod.RealNameRequired">请先前往 Natayark 账户中心进行实名验证再尝试操作!</sys:String>
<sys:String x:Key="Link.Mod.Task.CleanCache">清理缓存与冗余组件</sys:String>
<sys:String x:Key="Link.Mod.Task.DownloadEasyTier">下载 EasyTier</sys:String>
<sys:String x:Key="Link.Mod.Task.ExtractFiles">解压文件</sys:String>
<sys:String x:Key="Link.Mod.Task.FetchAnnouncement">大厅公告获取</sys:String>
<sys:String x:Key="Link.Mod.Task.InitLobby">大厅初始化</sys:String>
<sys:String x:Key="Link.Mod.Task.InitLobbyUi">大厅界面初始化</sys:String>
<sys:String x:Key="Link.Mod.Task.RefreshUi">刷新界面</sys:String>
<sys:String x:Key="Link.Mod.UsernameOrLogin">请先前往设置输入一个用户名,或登录至 Natayark Network 再进行联机!</sys:String>
<!-- Link.Nat -->
<!-- Link.Nat.Type -->
<sys:String x:Key="Link.Nat.Type.FullCone">中等(完全圆锥)</sys:String>
<sys:String x:Key="Link.Nat.Type.Open">开放</sys:String>
<sys:String x:Key="Link.Nat.Type.PortRestricted">中等(端口受限圆锥)</sys:String>
<sys:String x:Key="Link.Nat.Type.Restricted">中等(受限圆锥)</sys:String>
<sys:String x:Key="Link.Nat.Type.Symmetric">严格(对称)</sys:String>
<sys:String x:Key="Link.Nat.Type.SymmetricEasy">严格(宽松对称)</sys:String>
<sys:String x:Key="Link.Nat.Type.SymmetricFirewall">严格(对称防火墙)</sys:String>
<sys:String x:Key="Link.Nat.Type.UdpBlocked">严格(阻止 UDP</sys:String>
<sys:String x:Key="Link.Nat.Type.Unknown">未知</sys:String>
<!-- LogPage -->
<sys:String x:Key="LogPage.Action.Clear">清空</sys:String>
<sys:String x:Key="LogPage.Action.Export">导出</sys:String>
<sys:String x:Key="LogPage.Action.ExportStack">导出运行栈</sys:String>
<sys:String x:Key="LogPage.Action.GameClosed">已关闭游戏 {0}</sys:String>
<sys:String x:Key="LogPage.Action.KillProcess">结束进程</sys:String>
<sys:String x:Key="LogPage.Action.Scroll">滚动</sys:String>
<sys:String x:Key="LogPage.Empty.Description">请点击 实例设置 → 概览 → 测试游戏 以显示游戏的实时日志。</sys:String>
<sys:String x:Key="LogPage.Empty.Title">无实时日志</sys:String>
<sys:String x:Key="LogPage.Error.OperationFailed">日志页面操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="LogPage.Export.GameLog.FileName">游戏日志 - {0}.log</sys:String>
<sys:String x:Key="LogPage.Export.GameLog.Filter">游戏日志(*.log)|*.log</sys:String>
<sys:String x:Key="LogPage.Export.SelectLocation">选择导出位置</sys:String>
<sys:String x:Key="LogPage.Export.Success">日志已导出!</sys:String>
<sys:String x:Key="LogPage.ExportStack.FileName">游戏运行栈 - {0}.log</sys:String>
<sys:String x:Key="LogPage.ExportStack.Filter">游戏运行栈(*.log)|*.log</sys:String>
<sys:String x:Key="LogPage.ExportStack.Progress">正在导出运行栈,请稍等(可能需要 15 秒 ~ 1 分钟)</sys:String>
<sys:String x:Key="LogPage.ExportStack.Success">运行栈已导出!</sys:String>
<sys:String x:Key="LogPage.Left.InstancesTitle">测试实例列表</sys:String>
<sys:String x:Key="LogPage.Level.Debug">Debug</sys:String>
<sys:String x:Key="LogPage.Level.Error">Error</sys:String>
<sys:String x:Key="LogPage.Level.Fatal">Fatal</sys:String>
<sys:String x:Key="LogPage.Level.Info">Info</sys:String>
<sys:String x:Key="LogPage.Level.Warn">Warn</sys:String>
<sys:String x:Key="LogPage.MaxLines.Hint">实时日志默认只保留 {0} 行,你可以在 实时日志行数 设置中修改!</sys:String>
<sys:String x:Key="LogPage.MaxLines.Title">最大行数</sys:String>
<sys:String x:Key="LogPage.MaxLines.ToolTip">实时日志功能中最大显示行数,超过该值会自动删除旧的日志。</sys:String>
<sys:String x:Key="LogPage.MaxLines.Unlimited">无限制</sys:String>
<sys:String x:Key="LogPage.Title">实时日志</sys:String>
<!-- Main -->
<sys:String x:Key="Main.April.Nope">木大!</sys:String>
<sys:String x:Key="Main.DebugMode.Hint">[调试模式] PCL CE 正以调试模式运行,这可能会导致性能下降,若无必要请不要开启!</sys:String>
<sys:String x:Key="Main.Error.OperationFailed">启动器界面操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Main.Error.ScrollToTopFailed">无法返回顶部。</sys:String>
<sys:String x:Key="Main.Eula.Message">在使用 PCL CE 前,请同意 PCL 的用户协议与免责声明。</sys:String>
<sys:String x:Key="Main.Eula.Title">协议授权</sys:String>
<sys:String x:Key="Main.Eula.View">查看用户协议与免责声明</sys:String>
<sys:String x:Key="Main.Exit.HasDownloadingTask">还有下载任务尚未完成,是否确定退出?</sys:String>
<sys:String x:Key="Main.Extra.BackToTop.ToolTip">返回顶部</sys:String>
<sys:String x:Key="Main.Extra.GameLog.ToolTip">游戏日志</sys:String>
<sys:String x:Key="Main.Extra.GiveUp.ToolTip">投降</sys:String>
<sys:String x:Key="Main.Extra.ShutdownMinecraft.ToolTip">关闭 Minecraft</sys:String>
<sys:String x:Key="Main.Extra.TaskManager.ToolTip">任务管理</sys:String>
<sys:String x:Key="Main.Extra.UpdateRestart.ToolTip">重启 PCL CE 以应用软件更新</sys:String>
<sys:String x:Key="Main.Extra.UpdateRestart.ToolTipWithVersion">重启 PCL CE 以应用软件更新 ({0} → {1})</sys:String>
<sys:String x:Key="Main.HiddenFeature.Disabled">功能隐藏设置已暂时关闭!</sys:String>
<sys:String x:Key="Main.HiddenFeature.Enabled">功能隐藏设置已重新开启!</sys:String>
<sys:String x:Key="Main.ShutdownMinecraft.Success">已关闭运行中的 Minecraft</sys:String>
<sys:String x:Key="Main.SpecialVersion.CiHint">当前运行的 PCL CE 为 CI 自动构建版本。&#xA;该版本包含最新的漏洞修复、优化和新特性,但性能和稳定性较差,不适合日常使用和制作整合包。&#xA;&#xA;除非社区开发者要求或您自己想要这么做,否则请下载最新稳定版使用。</sys:String>
<sys:String x:Key="Main.SpecialVersion.DebugHint">当前运行的 PCL CE 为 Debug 版本。&#xA;该版本仅适合开发者调试运行,可能会有严重的性能下降以及各种奇怪的网络问题。&#xA;&#xA;非开发者用户使用该版本造成的一切问题均不被社区支持,相关 issue 可能会被直接关闭。&#xA;除非您是开发者,否则请立即删除该版本,并下载最新稳定版使用。</sys:String>
<sys:String x:Key="Main.SpecialVersion.HideHintNotice">可以添加 PCL_DISABLE_DEBUG_HINT 环境变量 (任意值) 来隐藏这个提示。</sys:String>
<sys:String x:Key="Main.SpecialVersion.IUnderstand">我清楚我在做什么</sys:String>
<sys:String x:Key="Main.SpecialVersion.OpenDownloadPageAndExit">打开最新版下载页并退出</sys:String>
<sys:String x:Key="Main.SpecialVersion.Title">特殊版本提示</sys:String>
<sys:String x:Key="Main.Tab.Download">下载</sys:String>
<sys:String x:Key="Main.Tab.Launch">启动</sys:String>
<sys:String x:Key="Main.Tab.Settings">设置</sys:String>
<sys:String x:Key="Main.Tab.Tools">工具</sys:String>
<sys:String x:Key="Main.Telemetry.Message">启用遥测数据收集后,启动器将会收集并上报错误与设备环境信息,这可以帮助开发者修复潜在的问题、更好的进行规划和开发。&#xA;若启用此功能,我们将会收集以下信息:&#xA;&#xA;- 启动器内出现的错误&#xA;- 启动器版本信息与识别码&#xA;- Windows 系统版本与架构&#xA;- 已安装的物理内存大小&#xA;- NAT 与 IPv6 支持情况&#xA;- 是否使用过原版 PCL、HMCL 或 BakaXL&#xA;&#xA;这些数据均不与你关联,我们也绝不会向第三方出售数据。&#xA;如果不希望启用遥测,可以选择拒绝。这不会影响其他功能的正常使用,但可能会影响开发者修复潜在 Bug。&#xA;你可以随时在启动器设置中调整这项设置。</sys:String>
<sys:String x:Key="Main.Telemetry.Title">启用遥测数据收集</sys:String>
<sys:String x:Key="Main.Title.GameLog">实时日志</sys:String>
<sys:String x:Key="Main.Title.InstanceSelect">实例选择</sys:String>
<sys:String x:Key="Main.Title.InstanceSetup">实例设置 - {0}</sys:String>
<sys:String x:Key="Main.Title.ResourceDownload">资源下载 - {0}</sys:String>
<sys:String x:Key="Main.Title.SaveManagement">存档管理 - {0}</sys:String>
<sys:String x:Key="Main.Title.TaskManager">任务管理</sys:String>
<sys:String x:Key="Main.UpdateLog.Empty">欢迎使用呀~</sys:String>
<sys:String x:Key="Main.UpdateLog.FullChangelog">完整更新日志</sys:String>
<sys:String x:Key="Main.UpdateLog.Title">PCL CE 已更新至 {0} {1}</sys:String>
<sys:String x:Key="Main.HomepageReset.Title">主页预设已重置</sys:String>
<sys:String x:Key="Main.HomepageReset.Content">由于启动器更新,已重置主页预设。请重新前往 设置 - 个性化 选择预设</sys:String>
<!-- Main.FileDrag -->
<sys:String x:Key="Main.FileDrag.AuthlibInvalid">输入的 Authlib 验证服务器不符合网址格式({0})!</sys:String>
<sys:String x:Key="Main.FileDrag.CreateAuthlibProfile">是否要创建新的第三方验证档案?&#xA;验证服务器地址:{0}</sys:String>
<sys:String x:Key="Main.FileDrag.CreateAuthlibProfileTitle">创建新的第三方验证档案</sys:String>
<sys:String x:Key="Main.FileDrag.ExtractFirst">请将文件解压后再拖入!</sys:String>
<sys:String x:Key="Main.FileDrag.FileNotFound">拖入的文件不存在:{0}</sys:String>
<sys:String x:Key="Main.FileDrag.FileOnly">请拖入一个文件,而非文件夹!</sys:String>
<sys:String x:Key="Main.FileDrag.HomepageExists">已存在一个主页文件,是否要将它覆盖?</sys:String>
<sys:String x:Key="Main.FileDrag.HomepageLoaded">已加载主页自定义文件!</sys:String>
<sys:String x:Key="Main.FileDrag.Imported">已导入 {0}</sys:String>
<sys:String x:Key="Main.FileDrag.OverwriteTitle">覆盖确认</sys:String>
<sys:String x:Key="Main.FileDrag.SameFileExists">已存在同名文件:{0}</sys:String>
<sys:String x:Key="Main.FileDrag.SameFolderExists">发现同名文件夹,无法粘贴:{0}</sys:String>
<sys:String x:Key="Main.FileDrag.SameTypeOnly">一次请只拖入相同类型的文件!</sys:String>
<sys:String x:Key="Main.FileDrag.SaveImportFailed">导入存档失败</sys:String>
<sys:String x:Key="Main.FileDrag.SaveInvalid">导入后的存档缺少 level.dat</sys:String>
<sys:String x:Key="Main.FileDrag.SaveNotFound">压缩包内没有可导入的 Minecraft 存档!</sys:String>
<sys:String x:Key="Main.FileDrag.UnknownOperation">PCL CE 无法确定应当执行的文件拖拽操作……</sys:String>
<!-- Minecraft -->
<sys:String x:Key="Minecraft.Comp.Error.OperationFailed">组件操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Minecraft.Folder.Error.OperationFailed">Minecraft 文件夹操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Minecraft.Skin.Error.InvalidSkinType">皮肤地址种类无效:{0}</sys:String>
<sys:String x:Key="Minecraft.Skin.Error.NoCustomSkin">用户未设置自定义皮肤</sys:String>
<sys:String x:Key="Minecraft.Skin.Error.NoSkinData">皮肤返回值中不包含皮肤数据项,可能是未设置自定义皮肤的用户</sys:String>
<sys:String x:Key="Minecraft.Skin.Error.OfflineNoSkin">离线 Uuid 无正版皮肤文件。</sys:String>
<sys:String x:Key="Minecraft.Skin.Error.PropertyNotFound">未从皮肤返回值中找到符合条件的 Property</sys:String>
<sys:String x:Key="Minecraft.Skin.Error.SkinReturnEmpty">皮肤返回值为空,可能是未设置自定义皮肤的用户</sys:String>
<sys:String x:Key="Minecraft.Skin.Error.UuidEmpty">Uuid 为空。</sys:String>
<sys:String x:Key="Minecraft.Update.NewVersion">新版本:{0}</sys:String>
<sys:String x:Key="Minecraft.Update.Title">Minecraft 更新提示</sys:String>
<sys:String x:Key="Minecraft.Update.UpdateTime">更新时间:</sys:String>
<sys:String x:Key="Minecraft.Update.UpdatedAt">更新于:</sys:String>
<sys:String x:Key="Minecraft.Version.Unknown">未知版本</sys:String>
<sys:String x:Key="Minecraft.Version.Vanilla">原版</sys:String>
<!-- Minecraft.Download -->
<sys:String x:Key="Minecraft.Download.Loader.NewForge">新版 Forge</sys:String>
<sys:String x:Key="Minecraft.Download.ServerBatch.InstructionEula">如果提示 "You need to agree to the EULA",请编辑 eula.txt 同意协议!</sys:String>
<sys:String x:Key="Minecraft.Download.ServerBatch.InstructionJavaPath">如果服务端立即停止,请右键编辑该脚本,使用!JAVA_HOME!替换%JAVA_HOME%</sys:String>
<sys:String x:Key="Minecraft.Download.ServerBatch.InstructionPclSettings">你可以在 PCL CE 的 [设置 → 启动选项 → 启动器内 Java 路径] 中将此版本选为合适的 Java 并启用!</sys:String>
<sys:String x:Key="Minecraft.Download.ServerBatch.ServerStopped">服务端已停止。</sys:String>
<sys:String x:Key="Minecraft.Download.ServerBatch.Title">{0} 原版服务端</sys:String>
<!-- Minecraft.Download.Error -->
<sys:String x:Key="Minecraft.Download.Error.AddClientVersionFailed">添加客户端版本失败</sys:String>
<sys:String x:Key="Minecraft.Download.Error.AssetIndexAnalysisFailed">分析资源文件索引地址失败</sys:String>
<sys:String x:Key="Minecraft.Download.Error.BmclapiMissingTargetVersion">BMCLAPI 源未包含目标版本 {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Error.FabricMetaDownloadFailed">Fabric meta 下载失败: {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Error.ForgeOldInstallFailed">非新版方式安装 Forge 失败</sys:String>
<sys:String x:Key="Minecraft.Download.Error.ForgePromosResultTooShort">获取的结果过短({0}</sys:String>
<sys:String x:Key="Minecraft.Download.Error.ForgeRecommendedTooFew">获取的推荐版本数过少({0}</sys:String>
<sys:String x:Key="Minecraft.Download.Error.InstallationCanceled">已取消安装。</sys:String>
<sys:String x:Key="Minecraft.Download.Error.InstallerFailedLastLine">安装器运行出错,末行为 {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Error.InstanceAlreadyExists">实例 {0} 已存在,是否重新下载?{1}这会覆盖实例的 Json 和 Jar 文件,但不会影响实例隔离的文件。</sys:String>
<sys:String x:Key="Minecraft.Download.Error.InstanceAlreadyExists.Title">实例已存在</sys:String>
<sys:String x:Key="Minecraft.Download.Error.InstanceDownloading">该实例正在下载中!</sys:String>
<sys:String x:Key="Minecraft.Download.Error.JavaNotFoundInstallCanceled">由于未找到 Java,已取消安装。</sys:String>
<sys:String x:Key="Minecraft.Download.Error.JavaVersionRequired">Java 8 或更高版本</sys:String>
<sys:String x:Key="Minecraft.Download.Error.JsonInvalid">{0} Json 有误,地址:{1},前段内容:{2}</sys:String>
<sys:String x:Key="Minecraft.Download.Error.LiteLoaderInstallFailed">安装新 LiteLoader 实例失败</sys:String>
<sys:String x:Key="Minecraft.Download.Error.LoaderDetailsNotFound">未能找到 {0} {1}-{2} 的详细信息!</sys:String>
<sys:String x:Key="Minecraft.Download.Error.LoaderInstallFailed">安装新 {0} 实例失败</sys:String>
<sys:String x:Key="Minecraft.Download.Error.LoaderInstallerFailedLastLine">{0} 安装器出错,日志结束部分为:{1}</sys:String>
<sys:String x:Key="Minecraft.Download.Error.LoaderInstallerRunFailed">运行 {0} 安装器失败</sys:String>
<sys:String x:Key="Minecraft.Download.Error.LoaderLibraryListFailed">获取{0}支持库列表失败</sys:String>
<sys:String x:Key="Minecraft.Download.Error.NoAvailableVersion">没有可用版本</sys:String>
<sys:String x:Key="Minecraft.Download.Error.NoJarDownloadInfo">底层实例 {0} 中无 Jar 文件下载信息</sys:String>
<sys:String x:Key="Minecraft.Download.Error.NoJsonDownloadAddress">无对应的 json 下载地址</sys:String>
<sys:String x:Key="Minecraft.Download.Error.NoOfficialServerDownload">Mojang 没有给 Minecraft {0} 提供官方服务端下载,没法下,撤退!</sys:String>
<sys:String x:Key="Minecraft.Download.Error.NoVanillaLoaderSpecified">如果没有指定原版下载器,则不能指定 MC 安装文件夹</sys:String>
<sys:String x:Key="Minecraft.Download.Error.NotFound">无可用版本</sys:String>
<sys:String x:Key="Minecraft.Download.Error.OperationFailed">Minecraft 下载操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Minecraft.Download.Error.OptiFineForgeCompatMismatch">版本与 Forge 兼容数据无法对应</sys:String>
<sys:String x:Key="Minecraft.Download.Error.OptiFineInstallFailed.MethodA">安装 OptiFine(方式 A)失败</sys:String>
<sys:String x:Key="Minecraft.Download.Error.OptiFineInstallFailed.MethodB">安装 OptiFine(方式 B)失败</sys:String>
<sys:String x:Key="Minecraft.Download.Error.OptiFineInstallerRunFailed">运行 OptiFine 安装器失败</sys:String>
<sys:String x:Key="Minecraft.Download.Error.OptiFineTimeDataMismatch">版本与发布时间数据无法对应</sys:String>
<sys:String x:Key="Minecraft.Download.Error.ServerDownloading">该服务端正在下载中!</sys:String>
<sys:String x:Key="Minecraft.Download.Error.Timeout">下载源连接超时</sys:String>
<sys:String x:Key="Minecraft.Download.Error.UnknownMinecraftVersion">未知的 Minecraft 版本({0}</sys:String>
<sys:String x:Key="Minecraft.Download.Error.VersionListOperationFailed">{0} 版本列表解析失败({1}</sys:String>
<!-- Minecraft.Download.Modpack -->
<sys:String x:Key="Minecraft.Download.Modpack.BundledLauncher.DoNotUse">不换用</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.BundledLauncher.Message">整合包里似乎自带了启动器,是否换用它继续安装?&#xD;&#xA;即将打开:{0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.BundledLauncher.Title">换用整合包启动器?</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.BundledLauncher.UseBundled">换用</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.EncryptedArchiveUnsupported">PCL CE 无法处理加密的压缩包,请在解压后重新压缩为不加密的 zip 格式再试</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Error.OperationFailed">整合包操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.FileDialog.Filter">整合包文件(*.rar;*.zip;*.mrpack)|*.rar;*.zip;*.mrpack</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.FileDialog.Title">选择整合包压缩文件</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.InputInstanceName">输入实例名称</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Installing">该整合包正在安装中!</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.InvalidGamePathChars">游戏路径中不能含有感叹号或分号:{0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.MissingGameVersion.Generic">该整合包未提供游戏版本信息,无法安装!</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.MissingGameVersion.Hmcl">该 HMCL 整合包未提供游戏版本信息,无法安装!</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.MissingGameVersion.McbbsAddons">该 MCBBS 整合包未提供游戏版本附加信息,无法安装!</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.ModMissingRequiredInfoSkipped">某项模组缺少必要信息,已跳过:{0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.OpenFailed">打开整合包文件失败</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.OptionalFile.Download">下载</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.OptionalFile.Message">是否要下载整合包中的可选文件 {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.OptionalFile.Skip">跳过</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.OptionalFile.Title">下载可选文件</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.PathOutsideInstance.Message">整合包的文件路径超出了实例文件夹,请向整合包作者反馈此问题!&#xD;&#xA;错误的文件:{0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.PathOutsideInstance.Title">文件路径校验失败</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.QuiltUnsupported">无法安装整合包:当前启动器已不再支持 Quilt 加载器。</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.RarUnsupported">PCL CE 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.SelectEmptyFolder.Message">接下来请选择一个空文件夹,它会被安装到这个文件夹里。</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.SelectTargetFolder.Title">选择安装目标(必须是一个空文件夹)</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.SomeModsDeleted">整合包中的部分模组版本已被模组作者删除,所以没法继续安装了,请向整合包作者反馈该问题</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.TargetFolderMustBeEmpty">请选择一个空文件夹作为安装目标!</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Task.CurseForgeInstall">CurseForge 整合包安装:{0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Task.HmclInstall">HMCL 整合包安装:{0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Task.McbbsInstall">MCBBS 整合包安装:{0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Task.MmcInstall">MMC 整合包安装:{0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Task.ModrinthInstall">Modrinth 整合包安装:{0}</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.TooOldUnsupported">该整合包版本过老,已不支持进行安装!</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.UnknownArchiveStructure">未能找到适合的文件结构,这可能不是一个 MC 压缩包</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.UnknownLoader">无法安装整合包,其中出现了未知的模组加载器 {0}(版本为 {1})!</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.UnsupportedArchive">打开整合包文件失败,文件可能损坏或为不支持的压缩包格式</sys:String>
<!-- Minecraft.Download.Modpack.Stage -->
<sys:String x:Key="Minecraft.Download.Modpack.Stage.BuildModsDownloadInfo">构造模组下载信息</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.DownloadAdditions">下载额外文件</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.DownloadMods">下载模组</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.DownloadMods.MainLoader">下载模组(主加载器)</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.ExtractArchive">解压压缩包</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.ExtractModpack">解压整合包文件</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.FinalizeFiles">最终整理文件</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.GameInstall">游戏安装</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.ModpackInstall">整合包安装</sys:String>
<sys:String x:Key="Minecraft.Download.Modpack.Stage.PrepareModsDownloadInfo">获取模组下载信息</sys:String>
<!-- Minecraft.Download.Stage -->
<sys:String x:Key="Minecraft.Download.Stage.AddIgnoreFlag">添加忽略标识</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeAssetsIndex">分析资源文件索引地址</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeAssetsIndex.Background">后台分析资源文件索引地址</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeAssetsIndex.Side">分析资源文件索引地址(副加载器)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeCoreJarUrl">分析核心 Jar 文件下载地址</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeFabricLibraries">分析 Fabric 支持库文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeGameLibrariesSide">分析游戏支持库文件(副加载器)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeLabyModLibraries">分析 LabyMod 支持库文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeLegacyFabricLibraries">分析 Legacy Fabric 支持库文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeLiteLoaderLibraries">分析 LiteLoader 支持库文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeLoaderLibraries">分析 {0} 支持库文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeMissingAssets">分析缺失资源文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeMissingLibraries">分析缺失支持库文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeOptiFineLibraries">分析 OptiFine 支持库文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeRequiredAssets.Side">分析所需资源文件(副加载器)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeVanillaAndLabyModLibrariesSide">分析原版与 LabyMod 支持库文件(副加载器)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.AnalyzeVanillaLibraries.Side">分析原版支持库文件(副加载器)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.BuildServer">构建服务端</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.CopyAssetsIndex.Background">后台复制资源文件索引</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DeleteIgnoreFlag">删除忽略标识</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadAssets">下载资源文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadAssets.MainLoader">下载资源文件(主加载器)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadAssets.Side">下载资源文件(副加载器)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadAssetsIndex">下载资源文件索引</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadAssetsIndex.Background">后台下载资源文件索引</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadAssetsIndex.Side">下载资源文件索引(副加载器)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadCoreJar">下载核心 Jar 文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadFabricApi">下载 Fabric API</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadGameLibraries">下载游戏支持库文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadGameLibrariesSide">下载游戏支持库文件(副加载器)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadInstanceJson">下载实例 JSON 文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadLabyModClientJson">下载 LabyMod 客户端 JSON 文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadLegacyFabricApi">下载 Legacy Fabric API</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadLibraries">下载支持库文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadLibraries.MainLoader">下载支持库文件(主加载器)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadLiteLoaderLibraries">下载 LiteLoader 支持库文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadLoaderLibraries">下载 {0} 支持库文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadLoaderLibraries.Side">下载 {0} 支持库文件(副加载器)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadLoaderMainFile">下载 {0} 主文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadMainFile">下载主文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadOptiFabric">下载 OptiFabric</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadOptiFineLibraries">下载 OptiFine 支持库文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadOptiFineMainFile">下载 OptiFine 主文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadServerFile">下载服务端文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadVanillaAndLabyModLibrariesSide">下载原版与 LabyMod 支持库文件(副加载器)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadVanillaAssets">下载原版资源文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.DownloadVanillaLibraries.Side">下载原版支持库文件(副加载器)</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.FabricInstallerDownload">Fabric {0} 安装器下载</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ForgelikeDownload">{0} 下载</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ForgelikeInstallerFilter">{0} 安装器 (*.{1})|*.{1}</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.GetLoaderLibraries">获取 {0} 支持库文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.InstallForge.MethodA">安装 Forge(方式 A</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.InstallForge.MethodB">安装 Forge(方式 B</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.InstallForgeType">安装 {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.InstallGame">安装游戏</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.InstallLiteLoader">安装 LiteLoader</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.InstallOptiFine.MethodA">安装 OptiFine(方式 A</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.InstallOptiFine.MethodB">安装 OptiFine(方式 B</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.LabyModInstallerDownload">LabyMod 安装器下载</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.LegacyFabricInstallerDownload">Legacy Fabric {0} 安装器下载</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.LiteLoaderDownload">LiteLoader {0} 下载</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.LiteLoaderInstallerDownload">LiteLoader {0} 安装器下载</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.LoaderDownloadCombo">{0} {1} 下载</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.LoaderInstallerDownload">下载 {0} {1}</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.MinecraftDownload">Minecraft {0} 下载</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.MinecraftServerDownload">Minecraft {0} 服务端下载</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ObtainFabricMainFileUrl">获取 Fabric 主文件下载地址</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ObtainLabyModClientUrl">获取 LabyMod 客户端文件下载地址</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ObtainLegacyFabricMainFileUrl">获取 Legacy Fabric 主文件下载地址</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ObtainLoaderDetails">获取 {0} 详细信息</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ObtainOptiFineDownloadUrl">获取 OptiFine 下载地址</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ObtainOptiFineUrl">获取 OptiFine 主文件下载地址</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.ObtainVanillaJsonUrl">获取原版 Json 文件下载地址</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.OptiFineDownload">OptiFine {0} 下载</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.PrepareLoaderDownload">准备下载 {0}</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.StartLiteLoaderDependencyDownload">启动 LiteLoader 依赖实例下载</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.UpdateAssetsIndex.Background">后台更新资源文件索引</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.VanillaJsonDownload">下载原版 Json 文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.VanillaLibrariesDownload">下载原版支持库文件</sys:String>
<sys:String x:Key="Minecraft.Download.Stage.WaitVanillaDownload">等待原版下载</sys:String>
<!-- Minecraft.Error -->
<sys:String x:Key="Minecraft.Error.AssetIndexNotFound">未找到 Asset Index</sys:String>
<sys:String x:Key="Minecraft.Error.CannotReadInstanceFolder">无法读取实例文件夹,可能是由于没有权限({0}</sys:String>
<sys:String x:Key="Minecraft.Error.DependencyRecursion">版本依赖项出现嵌套:{0}</sys:String>
<sys:String x:Key="Minecraft.Error.InitInstanceJsonFailed">初始化实例 JSON 时失败({0}</sys:String>
<sys:String x:Key="Minecraft.Error.InstanceJsonNotFound">未找到实例 JSON 文件:{0}</sys:String>
<sys:String x:Key="Minecraft.Error.NoAssetIndexInfo">该实例不存在资源文件索引信息</sys:String>
<sys:String x:Key="Minecraft.Error.QuiltUnsupported">Quilt 不支持 Minecraft {0}</sys:String>
<sys:String x:Key="Minecraft.Error.Unrecognizable">无法识别:{0}</sys:String>
<sys:String x:Key="Minecraft.Error.UpdateNotify">Minecraft 更新提示发送失败({0}</sys:String>
<sys:String x:Key="Minecraft.Error.VersionNumberEmpty">无法获取版本号,结果为空</sys:String>
<sys:String x:Key="Minecraft.Error.VersionNumberFetchFailed">版本号获取失败:{0}</sys:String>
<!-- Minecraft.Fool -->
<sys:String x:Key="Minecraft.Fool.Tag.Blue">(蓝色版本)</sys:String>
<sys:String x:Key="Minecraft.Fool.Tag.Purple">(紫色版本)</sys:String>
<sys:String x:Key="Minecraft.Fool.Tag.Red">(红色版本)</sys:String>
<!-- Minecraft.Fool.Description -->
<sys:String x:Key="Minecraft.Fool.Description.2013">2013 | 这个秘密计划了两年的更新将游戏推向了一个新高度!</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2015">2015 | 作为一款全年龄向的游戏,我们需要和平,需要爱与拥抱。</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2016">2016 | 是时候将现代科技带入 Minecraft 了!</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2019">2019 | 我们从地下室的废墟里找到了这个开发于 1994 年的杰作!</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2020">2020 | 我们加入了 20 亿个新的维度,让无限的想象变成了现实!</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2022">2022 | 一次一个方块更新!迎接全新的挖掘、合成与骑乘玩法吧!</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2023">2023 | 研究表明:玩家喜欢作出选择——越多越好!</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2024">2024 | 毒马铃薯一直都被大家忽视和低估,于是我们超级加强了它!</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2025">2025 | 你可以合成任何东西——包括合成你的世界!</sys:String>
<sys:String x:Key="Minecraft.Fool.Description.2026">2026 | 为什么需要物品栏?让方块们跟着你走吧!</sys:String>
<!-- Minecraft.Launch -->
<sys:String x:Key="Minecraft.Launch.AccountVerification.Back">返回</sys:String>
<sys:String x:Key="Minecraft.Launch.AccountVerification.Demo">试玩</sys:String>
<sys:String x:Key="Minecraft.Launch.AccountVerification.Message">你必须先登录正版账号才能启动游戏!</sys:String>
<sys:String x:Key="Minecraft.Launch.AccountVerification.Purchase">购买正版</sys:String>
<sys:String x:Key="Minecraft.Launch.AccountVerification.Title">正版验证</sys:String>
<sys:String x:Key="Minecraft.Launch.Cancelled">已取消启动!</sys:String>
<sys:String x:Key="Minecraft.Launch.DemoMode">游戏将以试玩模式启动!</sys:String>
<sys:String x:Key="Minecraft.Launch.Donate.Decline">但是我拒绝</sys:String>
<sys:String x:Key="Minecraft.Launch.Donate.Message">PCL CE 已经为你启动了 {0} 次游戏啦!&#xA;如果 PCL CE 还算好用的话,也许可以考虑赞助一下 PCL 原作者……&#xA;如果没有大家的支持,PCL 很难在免费、无任何广告的情况下维持数年的更新(磕头)……!</sys:String>
<sys:String x:Key="Minecraft.Launch.Donate.Support">支持一下!</sys:String>
<sys:String x:Key="Minecraft.Launch.Donate.Title">{0} 次启动!</sys:String>
<sys:String x:Key="Minecraft.Launch.ExportScript.Cancelled">已取消导出启动脚本!</sys:String>
<sys:String x:Key="Minecraft.Launch.ExportScript.Success">导出启动脚本成功!</sys:String>
<sys:String x:Key="Minecraft.Launch.PurchaseHint.Later">下次一定</sys:String>
<sys:String x:Key="Minecraft.Launch.PurchaseHint.Message">看起来你似乎没买正版...&#xA;如果觉得 Minecraft 还不错,可以购买正版支持一下,毕竟开发游戏也真的很不容易...不要一直白嫖啦。&#xA;&#xA;在验证一个正版账号之后,就不会出现这个提示了!</sys:String>
<sys:String x:Key="Minecraft.Launch.PurchaseHint.Purchase">支持正版游戏!</sys:String>
<sys:String x:Key="Minecraft.Launch.PurchaseHint.Title">考虑一下正版?</sys:String>
<sys:String x:Key="Minecraft.Launch.Success">{0} 启动成功!</sys:String>
<!-- Minecraft.Launch.Error -->
<sys:String x:Key="Minecraft.Launch.Error.AlreadyLaunching">已有游戏正在启动中!</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.CancelProcess">取消启动结束进程失败</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.CannotConnectAuthServer">无法连接到第三方登录服务器({0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.CannotConnectAuthServerWithDetail">无法连接到第三方登录服务器({0})&#xA;详细信息:</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.CannotLaunch">无法启动 Minecraft{0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.CustomCommand">执行自定义命令失败</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.DispatcherUnavailable">启动器界面不可用,无法执行此操作。</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.ExportScriptFailed">导出启动脚本失败。请查看错误详情。</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.GpuSet">调整显卡设置失败,Minecraft 可能会使用默认显卡运行</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.InvalidState">错误的状态改变:{0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.LaunchFailed">Minecraft 启动失败。请查看错误详情。</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.MissingMainClass">实例 JSON 中没有 mainClass 项!</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.NativesCorrupted">无法打开 Natives 文件({0}),该文件可能已损坏,请重新尝试启动游戏</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.NoJava">没有可用的 Java,已取消启动!</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.OperationFailed">Minecraft 启动操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.OptiFineAutoJoinWarning">OptiFine 与自动进入服务器可能不兼容,有概率导致材质丢失甚至游戏崩溃!</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.PrioritySet">设置进程优先级失败</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.Proxy">添加代理信息到游戏失败,放弃加入</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.RefreshInfo">刷新启动信息失败</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.SpecialMessage.WithDetail">启动失败。&#xA;&#xA;错误信息:&#xA;{0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.UpdateOptionsFailed">更新 options.txt 失败。请查看错误详情。</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.UpdateProfilesFailed">更新 launcher_profiles.json 失败。请查看错误详情。</sys:String>
<sys:String x:Key="Minecraft.Launch.Error.WatcherOperationFailed">Minecraft 运行监控失败。请查看错误详情。</sys:String>
<!-- Minecraft.Launch.Java -->
<sys:String x:Key="Minecraft.Launch.Java.AutoDownload.Action">自动下载</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.AutoDownload.Message">PCL CE 未找到 {0},是否需要自动下载?&#xA;如果已经安装了 {0},可以在 设置 → 启动选项 → 游戏 Java 中手动导入。</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.AutoDownload.Title">自动下载 Java</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Compatibility.CheckFailed">检查 Java 兼容性失败。请查看错误详情。</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Compatibility.GlobalSelectedOutOfRange">全局指定的 Java{0})超出版本要求范围 [{1}, {2}],可能导致游戏崩溃。</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Compatibility.InstanceSelectedOutOfRange">实例指定的 Java{0})超出版本要求范围 [{1}, {2}],可能导致游戏崩溃。</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Compatibility.NoMaximum">无上限</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Compatibility.NoMinimum">无下限</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Compatibility.RelativePathSelectedOutOfRange">实例相对路径指定的 Java({0})超出版本要求范围 [{1}, {2}],可能导致游戏崩溃。</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.InstallManuallyHint">请自行搜索并安装</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.NeedJava7">你需要安装 Java 7 才能启动该版本。&#xA;请自行搜索并安装 Java 7,安装后在 设置 → 启动选项 → 游戏 Java 中重新搜索或导入。</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.NeedJava8U141OrLater">你需要安装 Java 8u141 或更高版本的 Java 8 才能启动该版本。&#xA;请自行搜索并安装,安装后在 设置 → 启动选项 → 游戏 Java 中重新搜索或导入。</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.NeedJava8U141ToU320">你需要安装 Java 8u141 ~ 8u320 才能启动该版本。&#xA;请自行搜索并安装,安装后在 设置 → 启动选项 → 游戏 Java 中重新搜索或导入。</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.NeedLegacyJavaFixerOrJava7">你需要先安装 LegacyJavaFixer 模组,或安装 Java 7 才能启动该版本。&#xA;请自行搜索并安装 Java 7,安装后在 设置 → 启动选项 → 游戏 Java 中重新搜索或导入。</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.NotFound.Manual.Message">PCL CE 未找到 {0}。&#xA;请自行搜索并安装 {0},安装后在 设置 → 启动选项 → 游戏 Java 中重新搜索或导入。</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.NotFound.Title">未找到 Java</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Task.Download">下载 Java</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Task.DownloadFiles">下载 Java 文件</sys:String>
<sys:String x:Key="Minecraft.Launch.Java.Task.GetDownloadInfo">获取 Java 下载信息</sys:String>
<!-- Minecraft.Launch.Login -->
<sys:String x:Key="Minecraft.Launch.Login.Continue">继续</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Error.Input">获取登录输入信息失败</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Failed">登录失败</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Hint">登录提示</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.IKnow">我知道了</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.PasswordRequired.Message">请在登录时选择 {0}其他登录方法{1},然后选择 {0}使用我的密码{1}。&#xA;如果没有该选项,请选择 {0}设置密码{1},设置完毕后再登录。</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.PasswordRequired.Relogin">重新登录</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.PasswordRequired.SetPassword">设置密码</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.PasswordRequired.Title">需要使用密码登录</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.RefreshAccountFailed.Message">启动器在尝试刷新账号信息时遇到了网络错误。&#xA;你可以选择取消,检查网络后再次启动,也可以选择忽略错误继续启动,但可能无法游玩部分服务器。</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.RefreshAccountFailed.Title">账号信息获取失败</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Register">注册</sys:String>
<!-- Minecraft.Launch.Login.Auth -->
<sys:String x:Key="Minecraft.Launch.Login.Auth.DetailPrefix">详细信息:</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.FailedTitle">第三方验证失败</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.InvalidProfile">选择的角色 {0} 无效!</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.LoginFailed">登录失败:{0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.LoginFailed.WithDetail">第三方验证登录失败。&#xA;&#xA;详细信息:&#xA;{0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.NetworkFailed.Message">第三方验证登录失败,请检查你的网络状况是否良好。</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.NetworkFailed.WithDetail">第三方验证登录失败,请检查网络连接。&#xA;&#xA;详细信息:&#xA;{0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.NoProfile">$你还没有创建角色,请在创建角色后再试!</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.NoProfileCannotSwitch">你还没有创建角色,无法更换!</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.OnlyOneProfile">你的账户中只有一个角色,无法更换!</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.RefreshFailed">刷新登录失败</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.RefreshFailed.WithDetail">刷新登录失败。&#xA;&#xA;详细信息:&#xA;{0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.SecondRefreshFailed">二轮刷新登录失败</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.SelectProfile">选择使用的角色</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.Timeout.DetailMessage">$登录失败:连接登录服务器超时。&#xA;请检查你的网络状况是否良好,或尝试使用 VPN!&#xA;详细信息:</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.Timeout.Message">登录失败:连接登录服务器超时。&#xA;请检查你的网络状况是否良好,或尝试使用 VPN</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.Timeout.WithDetail">登录失败:连接登录服务器超时。&#xA;请检查网络连接,或尝试使用 VPN。&#xA;&#xA;详细信息:&#xA;{0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Auth.ValidationFailed.WithDetail">验证登录失败。&#xA;&#xA;详细信息:&#xA;{0}</sys:String>
<!-- Minecraft.Launch.Login.Microsoft -->
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.AbnormalIp">当前 IP 的登录尝试异常。&#xA;如果你使用了 VPN 或加速器,请把它们关掉或更换节点后再试!</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.Banned">该账号似乎已被微软封禁,无法登录。</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.ChangeBirthDate.Message">请在打开的网页中修改账号的出生日期(至少改为 18 岁以上)。&#xA;在修改成功后等待一分钟,然后再回到 PCL CE,就可以正常登录了!</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.ChangeBirthDate.SupportMessage">请在打开的网页中修改账号的出生日期(至少改为 18 岁以上)。&#xA;在修改成功后等待一分钟,然后再回到 PCL CE,就可以正常登录了!</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.CreateProfile.Button">创建档案</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.CreateProfile.Message">请先创建 Minecraft 玩家档案,然后再重新登录。</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.NotPurchased">暂时无法获取到此账户信息,此账户可能没有购买 Minecraft Java Edition 或者账户的 Xbox Game Pass 已过期</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.ProfileAlreadyAdded">你已经添加了这个档案...</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.PurchaseMinecraft">购买 Minecraft</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.RegionBlocked">你的网络所在的国家或地区无法登录微软账号。&#xA;请使用加速器或 VPN。</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.TooManyRequests">登录尝试太过频繁,请等待几分钟后再试!</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.Underage.AgeOver13">13 岁以上</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.Underage.AgeUnder13">12 岁以下</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.Underage.Message">该账号年龄不足,你需要先修改出生日期,然后才能登录。&#xA;该账号目前填写的年龄是否在 13 岁以上?</sys:String>
<sys:String x:Key="Minecraft.Launch.Login.Microsoft.XboxNotRegistered">你尚未注册 Xbox 账户,请在注册后再登录。</sys:String>
<!-- Minecraft.Launch.Precheck -->
<sys:String x:Key="Minecraft.Launch.Precheck.AuthServerMismatch">当前档案使用的第三方验证服务器与实例要求使用的不一致,请使用符合要求的档案启动游戏!</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.Failed.WithDetail">启动前检查失败:{0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.InstanceError">Minecraft 存在问题:{0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.InvalidPathChars">游戏路径中不可包含 ! 或 ;{0}</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.NoInstance">未选择 Minecraft 实例!</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.NoProfile">请先选择一个档案再启动游戏!</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.NonAsciiPath.Back">返回处理</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.NonAsciiPath.Continue">继续</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.NonAsciiPath.Message">欲启动实例 "{0}" 的路径中存在可能影响游戏正常运行的字符(非 ASCII 字符),是否仍旧启动游戏?&#xA;&#xA;如果不清楚具体作用,你可以先选择 "继续",发现游戏在启动后很快出现崩溃的情况后再尝试修改游戏路径等操作</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.NonAsciiPath.Title">游戏路径检查</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.RequireMicrosoft">当前实例要求使用正版验证,请使用正版验证档案启动游戏!</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.RequireMicrosoftOrThirdParty">当前实例要求使用正版验证或第三方验证,请使用符合要求的档案启动游戏!</sys:String>
<sys:String x:Key="Minecraft.Launch.Precheck.RequireThirdParty">当前实例要求使用第三方验证,请使用第三方验证档案启动游戏!</sys:String>
<!-- Minecraft.Launch.Stage -->
<sys:String x:Key="Minecraft.Launch.Stage.CompleteFiles">补全文件</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.CustomCommand">执行自定义命令</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.End">结束处理</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.ExtractNatives">解压文件</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.GetArguments">获取启动参数</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.GetJava">获取 Java</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.Login">登录</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.PreLaunch">预启动处理</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.Root">Minecraft 启动</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.StartProcess">启动进程</sys:String>
<sys:String x:Key="Minecraft.Launch.Stage.WaitWindow">等待游戏窗口出现</sys:String>
<!-- Music -->
<sys:String x:Key="Music.Hint.Cleared">背景音乐已清除!</sys:String>
<sys:String x:Key="Music.Hint.Playing">正在播放:{0}</sys:String>
<sys:String x:Key="Music.Hint.Refreshed">背景音乐已刷新:{0}</sys:String>
<sys:String x:Key="Music.Hint.Replaying">重新播放:{0}</sys:String>
<sys:String x:Key="Music.ToolTip.Paused.Multiple">已暂停:{0}&#xA;左键恢复播放,右键播放下一曲。</sys:String>
<sys:String x:Key="Music.ToolTip.Paused.Single">已暂停:{0}&#xA;左键恢复播放,右键重新从头播放。</sys:String>
<sys:String x:Key="Music.ToolTip.Playing.Multiple">正在播放:{0}&#xA;左键暂停,右键播放下一曲。</sys:String>
<sys:String x:Key="Music.ToolTip.Playing.Single">正在播放:{0}&#xA;左键暂停,右键重新从头播放。</sys:String>
<!-- Music.Error -->
<sys:String x:Key="Music.Error.DeviceBusy">音频设备正被其他程序占用。请关闭占用程序后重启 PCL CE,以恢复音乐功能。</sys:String>
<sys:String x:Key="Music.Error.DeviceChanged">音频设备已发生变化。请重启 PCL CE,以恢复音乐功能。</sys:String>
<sys:String x:Key="Music.Error.NoAvailable">没有可以播放的音乐!</sys:String>
<sys:String x:Key="Music.Error.NotDetected">未检测到可用的背景音乐!</sys:String>
<sys:String x:Key="Music.Error.NotStarted">音乐播放尚未开始!</sys:String>
<sys:String x:Key="Music.Error.OperationFailed">初始化或刷新背景音乐失败。请查看错误详情。</sys:String>
<sys:String x:Key="Music.Error.PlaybackFailed">无法播放 {0}。</sys:String>
<sys:String x:Key="Music.Error.PropertyChangeUnsupported">无法播放 {0}PCL CE 不支持播放过程中变更音频属性的文件。</sys:String>
<sys:String x:Key="Music.Error.UnsupportedFormat">无法播放 {0}PCL CE 可能不支持此格式,请转换为 .wav、.mp3 或 .flac。</sys:String>
<!-- Select -->
<sys:String x:Key="Select.Error.OperationFailed">实例选择操作失败。请查看错误详情。</sys:String>
<!-- Select.Folder -->
<sys:String x:Key="Select.Folder.AccessDenied">添加文件夹失败:PCL CE 没有访问该文件夹的权限!</sys:String>
<sys:String x:Key="Select.Folder.AddExisting.Title">添加已有文件夹</sys:String>
<sys:String x:Key="Select.Folder.AddExisting.ToolTip">将一个已有的 Minecraft 文件夹添加到列表</sys:String>
<sys:String x:Key="Select.Folder.AddOrImport">添加或导入</sys:String>
<sys:String x:Key="Select.Folder.Added">文件夹 {0} 已添加!</sys:String>
<sys:String x:Key="Select.Folder.AlreadyInList">此文件夹已在列表中!</sys:String>
<sys:String x:Key="Select.Folder.CannotAddWhileDownloading">在下载任务进行时,无法添加游戏文件夹!</sys:String>
<sys:String x:Key="Select.Folder.CannotCreateWhileDownloading">在下载任务进行时,无法创建游戏文件夹!</sys:String>
<sys:String x:Key="Select.Folder.Cleanup.Keep">保留</sys:String>
<sys:String x:Key="Select.Folder.Cleanup.Message">是否需要清理 PCL CE 在该文件夹中的配置文件?&#xA;这包括各个实例的独立设置(如自定义图标、第三方登录配置)等,对游戏本身没有影响。</sys:String>
<sys:String x:Key="Select.Folder.Cleanup.Title">配置文件清理</sys:String>
<sys:String x:Key="Select.Folder.Clear">清空</sys:String>
<sys:String x:Key="Select.Folder.Clear.Confirm">确认清空</sys:String>
<sys:String x:Key="Select.Folder.Clear.FinalWarning">如果你在该文件夹中存放了除 MC 以外的其他文件,这些文件也会被一同清空!&#xA;继续清空会导致该文件夹中的所有文件永久丢失,请在仔细确认后再继续!&#xA;目标文件夹:{0}&#xA;&#xA;这是最后一次警告!</sys:String>
<sys:String x:Key="Select.Folder.Clear.FirstWarning">你确定要清空这个文件夹吗?&#xA;目标文件夹:{0}&#xA;&#xA;这会导致该文件夹中的所有存档与其他文件永久丢失,且不可恢复!</sys:String>
<sys:String x:Key="Select.Folder.Clear.InProgress">正在清空文件夹 {0}</sys:String>
<sys:String x:Key="Select.Folder.Clear.Success">已清空文件夹 {0}</sys:String>
<sys:String x:Key="Select.Folder.CreateNew.Title">新建 .minecraft 文件夹</sys:String>
<sys:String x:Key="Select.Folder.CreateNew.ToolTip">在 PCL CE 当前所在文件夹下创建新的 .minecraft 文件夹</sys:String>
<sys:String x:Key="Select.Folder.CreateSuccess">新建 .minecraft 文件夹成功!</sys:String>
<sys:String x:Key="Select.Folder.CurrentFolder">当前文件夹</sys:String>
<sys:String x:Key="Select.Folder.Delete.Confirm">确认删除</sys:String>
<sys:String x:Key="Select.Folder.Delete.FinalWarning">如果你在该文件夹中存放了除 MC 以外的其他文件,这些文件也会被一同删除!&#xA;继续删除会导致该文件夹中的所有文件永久丢失,请在仔细确认后再继续!&#xA;目标文件夹:{0}&#xA;&#xA;这是最后一次警告!</sys:String>
<sys:String x:Key="Select.Folder.Delete.FirstWarning">你确定要删除这个文件夹吗?&#xA;目标文件夹:{0}&#xA;&#xA;这会导致该文件夹中的所有存档与其他文件永久丢失,且不可恢复!</sys:String>
<sys:String x:Key="Select.Folder.Delete.InProgress">正在删除文件夹 {0}</sys:String>
<sys:String x:Key="Select.Folder.Delete.Success">已删除文件夹 {0}</sys:String>
<sys:String x:Key="Select.Folder.Delete.WarningTitle">删除警告</sys:String>
<sys:String x:Key="Select.Folder.ImportModpack.Title">导入整合包</sys:String>
<sys:String x:Key="Select.Folder.ImportModpack.ToolTip">在当前选择的 Minecraft 文件夹下安装整合包</sys:String>
<sys:String x:Key="Select.Folder.InputDisplayName.Message">输入该文件夹在左边栏列表中显示的名称。</sys:String>
<sys:String x:Key="Select.Folder.InputDisplayName.Title">输入显示名称</sys:String>
<sys:String x:Key="Select.Folder.Invalid">无效的 Minecraft 文件夹:{0}</sys:String>
<sys:String x:Key="Select.Folder.Invalid.WithDetail">无效的 Minecraft 文件夹:{0}&#xA;&#xA;详细信息:&#xA;{1}</sys:String>
<sys:String x:Key="Select.Folder.InvalidMessage">Minecraft 文件夹失效</sys:String>
<sys:String x:Key="Select.Folder.InvalidPathChars">Minecraft 文件夹路径中不能含有感叹号或分号!</sys:String>
<sys:String x:Key="Select.Folder.InvalidTitle">Minecraft 文件夹失效</sys:String>
<sys:String x:Key="Select.Folder.ListTitle">文件夹列表</sys:String>
<sys:String x:Key="Select.Folder.MoveDown">下移</sys:String>
<sys:String x:Key="Select.Folder.MoveUp">上移</sys:String>
<sys:String x:Key="Select.Folder.NameUpdated">文件夹名称已更新为 {0}</sys:String>
<sys:String x:Key="Select.Folder.OfficialLauncherFolder">官方启动器文件夹</sys:String>
<sys:String x:Key="Select.Folder.RemoveFromList">移出列表</sys:String>
<sys:String x:Key="Select.Folder.RemoveSuccess">文件夹 {0} 已从列表中移除!</sys:String>
<sys:String x:Key="Select.Folder.Rename">重命名</sys:String>
<sys:String x:Key="Select.Folder.Rename.Title">输入新名称</sys:String>
<sys:String x:Key="Select.Folder.RestoreName">复原名称</sys:String>
<sys:String x:Key="Select.Folder.RestoreSuccess">文件夹名称已复原!</sys:String>
<sys:String x:Key="Select.Folder.SwitchBlockedByDownload">在下载任务进行时,无法切换游戏文件夹!</sys:String>
<!-- Select.Folder.Error -->
<sys:String x:Key="Select.Folder.Error.Add">添加文件夹失败({0}</sys:String>
<sys:String x:Key="Select.Folder.Error.AddNew">向文件夹列表中添加新文件夹失败</sys:String>
<sys:String x:Key="Select.Folder.Error.Cleanup">清理残留的忽略项目失败({0}</sys:String>
<sys:String x:Key="Select.Folder.Error.DragDrop">拖拽文件夹排序操作失败</sys:String>
<sys:String x:Key="Select.Folder.Error.InstallPack">自动安装整合包失败:{0}</sys:String>
<sys:String x:Key="Select.Folder.Error.Load">加载 Minecraft 文件夹列表失败</sys:String>
<sys:String x:Key="Select.Folder.Error.Operate">{0}文件夹 {1} 失败</sys:String>
<sys:String x:Key="Select.Folder.Error.Remove">从列表中移除游戏文件夹失败</sys:String>
<sys:String x:Key="Select.Folder.Error.Rename">重命名文件夹失败</sys:String>
<!-- Select.Instance -->
<sys:String x:Key="Select.Instance.Delete.ConfirmMessage">你确定要删除实例 {0} 吗?</sys:String>
<sys:String x:Key="Select.Instance.Delete.ConfirmPermanentMessage">你确定要永久删除实例 {0} 吗?</sys:String>
<sys:String x:Key="Select.Instance.Delete.ConfirmTitle">实例删除确认</sys:String>
<sys:String x:Key="Select.Instance.Delete.IsolatedWarning">由于该实例开启了实例隔离,删除时该实例对应的存档、资源包、模组等文件也将被一并删除!</sys:String>
<sys:String x:Key="Select.Instance.Delete.PermanentSuccess">实例 {0} 已永久删除!</sys:String>
<sys:String x:Key="Select.Instance.Delete.RecycleBinSuccess">实例 {0} 已删除到回收站!</sys:String>
<sys:String x:Key="Select.Instance.Empty.Download">下载游戏</sys:String>
<sys:String x:Key="Select.Instance.Empty.Message">未找到任何游戏实例,请先下载一个游戏实例。&#xA;若有已存在的实例,请在左边的列表中选择添加文件夹,选择 .minecraft 文件夹将其导入。</sys:String>
<sys:String x:Key="Select.Instance.Empty.Title">无可用实例</sys:String>
<sys:String x:Key="Select.Instance.EmptySearch.Title">无匹配的游戏实例</sys:String>
<sys:String x:Key="Select.Instance.Error.Classify">分类实例列表失败</sys:String>
<sys:String x:Key="Select.Instance.Error.Delete">删除实例 {0} 失败</sys:String>
<sys:String x:Key="Select.Instance.Error.IconLoad">加载实例图标失败</sys:String>
<sys:String x:Key="Select.Instance.Error.ListLoad">加载 .minecraft 实例列表失败</sys:String>
<sys:String x:Key="Select.Instance.Error.Load">加载实例失败({0}</sys:String>
<sys:String x:Key="Select.Instance.Error.UiUpdate">将实例列表转换显示时失败</sys:String>
<sys:String x:Key="Select.Instance.Favorite">收藏</sys:String>
<sys:String x:Key="Select.Instance.Hidden.EmptyMessage">没有实例被隐藏,你可以在实例设置的实例分类选项中隐藏实例。&#xA;再次按下 F11 即可退出隐藏实例查看模式。</sys:String>
<sys:String x:Key="Select.Instance.Hidden.EmptySearchTitle">无匹配的隐藏实例</sys:String>
<sys:String x:Key="Select.Instance.Hidden.EmptyTitle">无隐藏实例</sys:String>
<sys:String x:Key="Select.Instance.Loading">正在获取实例列表</sys:String>
<sys:String x:Key="Select.Instance.OpenFolder">打开实例目录</sys:String>
<sys:String x:Key="Select.Instance.Search.EmptyInput">请输入搜索内容</sys:String>
<sys:String x:Key="Select.Instance.Search.Hint">搜索游戏实例</sys:String>
<sys:String x:Key="Select.Instance.Search.NoHiddenResult">没有找到与 '{0}' 匹配的隐藏实例</sys:String>
<sys:String x:Key="Select.Instance.Search.NoResult">没有找到与 '{0}' 匹配的实例</sys:String>
<sys:String x:Key="Select.Instance.Search.ToolTip">输入实例名称或 [游戏版本号] [模组加载器] 进行搜索</sys:String>
<sys:String x:Key="Select.Instance.Settings">设置</sys:String>
<sys:String x:Key="Select.Instance.Unfavorite">取消收藏</sys:String>
<!-- Select.Instance.Card -->
<sys:String x:Key="Select.Instance.Card.AprilFools">愚人节版本</sys:String>
<sys:String x:Key="Select.Instance.Card.Cleanroom">Cleanroom 实例</sys:String>
<sys:String x:Key="Select.Instance.Card.Error">错误的实例</sys:String>
<sys:String x:Key="Select.Instance.Card.Fabric">Fabric 实例</sys:String>
<sys:String x:Key="Select.Instance.Card.Favorites">收藏夹</sys:String>
<sys:String x:Key="Select.Instance.Card.Forge">Forge 实例</sys:String>
<sys:String x:Key="Select.Instance.Card.Hidden">隐藏的实例</sys:String>
<sys:String x:Key="Select.Instance.Card.LabyMod">LabyMod 实例</sys:String>
<sys:String x:Key="Select.Instance.Card.LessUsed">不常用实例</sys:String>
<sys:String x:Key="Select.Instance.Card.LiteLoader">LiteLoader 实例</sys:String>
<sys:String x:Key="Select.Instance.Card.Modable">可安装模组</sys:String>
<sys:String x:Key="Select.Instance.Card.NeoForge">NeoForge 实例</sys:String>
<sys:String x:Key="Select.Instance.Card.Quilt">Quilt 实例</sys:String>
<sys:String x:Key="Select.Instance.Card.Regular">常规实例</sys:String>
<!-- Select.Instance.Description -->
<sys:String x:Key="Select.Instance.Description.AprilFools">愚人节版本 {0}</sys:String>
<sys:String x:Key="Select.Instance.Description.ExperimentalSnapshot">实验性快照{0}</sys:String>
<sys:String x:Key="Select.Instance.Description.ExperimentalSnapshot.Pending">实验性快照</sys:String>
<sys:String x:Key="Select.Instance.Description.NeedInherit">需要安装 {0} 作为前置实例</sys:String>
<sys:String x:Key="Select.Instance.Description.NoPermission">PCL CE 没有对该文件夹的访问权限,请右键以管理员身份运行 PCL CE</sys:String>
<sys:String x:Key="Select.Instance.Description.NotFound">未找到实例 {0}</sys:String>
<sys:String x:Key="Select.Instance.Description.NotLoaded">该实例未被加载,请向开发者反馈此问题</sys:String>
<sys:String x:Key="Select.Instance.Description.Old">远古版本</sys:String>
<sys:String x:Key="Select.Instance.Description.PreRelease">预发布版 {0}</sys:String>
<sys:String x:Key="Select.Instance.Description.Release">正式版 {0}</sys:String>
<sys:String x:Key="Select.Instance.Description.Release.Unknown">正式版</sys:String>
<sys:String x:Key="Select.Instance.Description.ReleaseCandidate">发布候选 {0}</sys:String>
<sys:String x:Key="Select.Instance.Description.ReportUnknownError">发生了未知错误,请向作者反馈此问题</sys:String>
<sys:String x:Key="Select.Instance.Description.Snapshot">快照版 {0}</sys:String>
<sys:String x:Key="Select.Instance.Description.Snapshot.Unknown">快照版</sys:String>
<sys:String x:Key="Select.Instance.Description.UnknownError">未知错误</sys:String>
<sys:String x:Key="Select.Instance.Description.UnknownMcVersion">PCL CE 无法识别该版本的 MC 版本号</sys:String>
<!-- Setup -->
<sys:String x:Key="Setup.Error.OperationFailed">设置操作失败。请查看错误详情。</sys:String>
<!-- Setup.About -->
<sys:String x:Key="Setup.About.Community.Info">Plain Craft Launcher Community Edition 的开发团队!</sys:String>
<sys:String x:Key="Setup.About.Community.Name">PCL Community</sys:String>
<sys:String x:Key="Setup.About.Contributors.Title">贡献者们</sys:String>
<sys:String x:Key="Setup.About.Contributors.ViewMore">查看更多</sys:String>
<sys:String x:Key="Setup.About.Error.LoadContributorsFailed">加载贡献者信息失败</sys:String>
<sys:String x:Key="Setup.About.GitHubHome">GitHub 主页</sys:String>
<sys:String x:Key="Setup.About.Licenses.Title">许可与版权声明</sys:String>
<sys:String x:Key="Setup.About.Licenses.ViewLicense">查看许可文档</sys:String>
<sys:String x:Key="Setup.About.Licenses.ViewWebsite">查看来源网站</sys:String>
<sys:String x:Key="Setup.About.OriginalAuthor.Info">Plain Craft Launcher 的原作者!</sys:String>
<sys:String x:Key="Setup.About.OriginalAuthor.Name">龙腾猫跃</sys:String>
<sys:String x:Key="Setup.About.SourceCode">查看源代码</sys:String>
<sys:String x:Key="Setup.About.SponsorOriginalAuthor">赞助原作者</sys:String>
<sys:String x:Key="Setup.About.Title">关于</sys:String>
<sys:String x:Key="Setup.About.UpstreamLegal.CopyrightText">Copyright © 龙腾猫跃 2016. All Rights Reserved.&#xD;计算机软件著作权登记号:2020SR0875133&#xD;违法违规行为举报投诉邮箱:hexdragon@vip.qq.com&#xD;非 MINECRAFT 官方产品。未经 MOJANG 或 MICROSOFT 批准,也不与 MOJANG 或 MICROSOFT 关联。</sys:String>
<sys:String x:Key="Setup.About.UpstreamLegal.OpenSource">开源代码</sys:String>
<sys:String x:Key="Setup.About.UpstreamLegal.PrivacyText">本软件的 User Agent 中包含启动器的版本号,以统计各版本的用户数量。&#xD;除此之外,本软件不会向开发者上传任何其他信息。</sys:String>
<sys:String x:Key="Setup.About.UpstreamLegal.TermsAndDisclaimer">用户协议与免责声明</sys:String>
<sys:String x:Key="Setup.About.UpstreamLegal.Title">上游版权与法律信息</sys:String>
<sys:String x:Key="Setup.About.Version.Info">当前版本: %VERSION% (%BRANCH%, %VERSIONCODE%, %COMMIT_HASH%)</sys:String>
<!-- Setup.About.EasterEgg -->
<sys:String x:Key="Setup.About.EasterEgg.Bored.Message">你现在是不是超无聊的?</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Bored.No">并不是</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Bored.Response">那你还点啥……真是搞不懂。</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Bored.Title">咕咕咕?</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Bored.Yes">是的</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.ClickDisabled">还点,还点就不让你点了……</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Encouragement">嗯,加油吧,嗯……</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.HiddenTheme">隐藏主题 混乱黄 已……嗯不对,这是 PCL CE,应该没有这玩意……</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.NiceClick">点这个很好玩么……</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.NothingBehind">后面什么都没有了哦!</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.StillClicking">还点?</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.StillStaring">你咋还这么无聊啊?</sys:String>
<!-- Setup.About.EasterEgg.Tired -->
<sys:String x:Key="Setup.About.EasterEgg.Tired.Exhausted">累死了</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.FinallyGiveUp">好吧……不过后面是真的啥也没了,不用点了真的。</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.Message1">你真的不累么?</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.Message2">你真的真的不累么?</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.Message3">你真的真的真的不累么?</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.NotTired">真的不累</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.ReallyNotTired">真的真的真的不累</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.StopClicking">那你就别点了喂……后面真的真的真的什么都没有了!</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.Title1">温馨提示</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.Title2">超温馨的温馨提示</sys:String>
<sys:String x:Key="Setup.About.EasterEgg.Tired.Title3">超超超温馨的温馨提示</sys:String>
<!-- Setup.About.Legal -->
<sys:String x:Key="Setup.About.Legal.CommunityLobbyPrivacy">PCL CE 联机大厅隐私政策</sys:String>
<sys:String x:Key="Setup.About.Legal.CommunitySource">PCL CE 开源代码</sys:String>
<sys:String x:Key="Setup.About.Legal.CopyrightText">Copyright © 2026 PCL Community &amp; 龙腾猫跃.&#xD;非 MINECRAFT 官方产品。未经 MOJANG 或 MICROSOFT 批准,也不与 MOJANG 或 MICROSOFT 关联。</sys:String>
<sys:String x:Key="Setup.About.Legal.NatayarkTerms">Natayark OpenID 服务条款</sys:String>
<sys:String x:Key="Setup.About.Legal.OtherInfo">其他信息</sys:String>
<sys:String x:Key="Setup.About.Legal.PrivacyNotice">隐私说明</sys:String>
<sys:String x:Key="Setup.About.Legal.PrivacyText">本软件的 User Agent 中包含启动器的版本号,以统计各版本的用户数量。&#xD;除此之外,本软件不会向开发者上传任何其他信息。&#xD;* 联机与软硬件调查相关功能遵循其自身的隐私政策,与上述说明不通用。</sys:String>
<sys:String x:Key="Setup.About.Legal.Title">版权声明与法律信息</sys:String>
<!-- Setup.About.SpecialThanks -->
<sys:String x:Key="Setup.About.SpecialThanks.GoBlog">转到博客</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Info.Bmclapi">提供 BMCLAPI 镜像源和 Forge 安装工具,详见 https://bmclapi.bangbang93.com</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Info.Cloud">提供了 PCL CE 的相关云服务</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Info.IconDesign">设计并制作了 PCL CE 的图标</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Info.LinkModule">提供了联机模块</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Info.Mcim">提供了 MCIM 中国模组下载镜像源和帮助库图床!</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Info.Mcmod">提供了模组名称的中文翻译和更多相关信息!</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Name.Bangbang93">bangbang93</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Name.EasyTier">EasyTier</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Name.Emperormummy">Emperormummy</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Name.Mcmod">MC 百科</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Name.Pysio">Pysio @ Akaere Network</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Name.Yunmoan">云默安 @ 至远光辉</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Name.Z0z0r4">z0z0r4</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.OpenMcmod">打开百科</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.OpenWebsite">打开网站</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.SponsorMirror">赞助镜像源</sys:String>
<sys:String x:Key="Setup.About.SpecialThanks.Title">特别鸣谢</sys:String>
<!-- Setup.Feedback -->
<sys:String x:Key="Setup.Feedback.ErrorPrompt.Submit.Action">反馈</sys:String>
<sys:String x:Key="Setup.Feedback.ErrorPrompt.Submit.Message">{0}&#xA;&#xA;是否反馈此问题?如果不反馈,这个问题可能永远无法得到解决!</sys:String>
<sys:String x:Key="Setup.Feedback.ErrorPrompt.Update.Message">{0}&#xA;&#xA;将 PCL CE 更新至最新版或许可以解决这个问题。</sys:String>
<sys:String x:Key="Setup.Feedback.Item.Submitter">提交者:{0}{1}</sys:String>
<sys:String x:Key="Setup.Feedback.Item.Type">类型:{0}</sys:String>
<sys:String x:Key="Setup.Feedback.Item.ViewDetail">查看详情</sys:String>
<sys:String x:Key="Setup.Feedback.LoadFailed">无法获取到内容</sys:String>
<sys:String x:Key="Setup.Feedback.Loading">正在获取反馈列表</sys:String>
<sys:String x:Key="Setup.Feedback.Reminder.Message">若要汇报 Bug,请点击“打开文件夹”并上传 Launch-{0}-[一串数字].log 中包含错误信息的文件。&#xA;&#xA;游戏崩溃一般与启动器无关,请不要因为游戏崩溃而提交启动器反馈。</sys:String>
<sys:String x:Key="Setup.Feedback.Reminder.NotNeeded">不需要</sys:String>
<sys:String x:Key="Setup.Feedback.Reminder.Title">反馈提交提醒</sys:String>
<sys:String x:Key="Setup.Feedback.Submit.Description">在提交反馈之前,先查找一下是否存在重复反馈,如果有重复反馈,那么请不要再次提交反馈,这样会加剧维护者的负担……&#xA;注意:此页面未展示所有的反馈,如需查看更多反馈,请前往 GitHub。</sys:String>
<sys:String x:Key="Setup.Feedback.Submit.Open">前往反馈</sys:String>
<sys:String x:Key="Setup.Feedback.Submit.Title">提交反馈</sys:String>
<sys:String x:Key="Setup.Feedback.Unavailable.CheckFailed.Action">重新检查更新</sys:String>
<sys:String x:Key="Setup.Feedback.Unavailable.CheckFailed.Message">检查更新失败,因此无法提交反馈。&#xA;请连接互联网,检查更新后确认该问题在最新版中依然存在,再提交反馈。</sys:String>
<sys:String x:Key="Setup.Feedback.Unavailable.NotLatest.Action">更新</sys:String>
<sys:String x:Key="Setup.Feedback.Unavailable.NotLatest.Message">当前 PCL CE 不是最新版,因此无法提交反馈。&#xA;请更新后确认该问题在最新版中依然存在,再提交反馈。</sys:String>
<sys:String x:Key="Setup.Feedback.Unavailable.Title">无法提交反馈</sys:String>
<sys:String x:Key="Setup.Feedback.Uncategorized">未分类</sys:String>
<!-- Setup.Feedback.Status -->
<sys:String x:Key="Setup.Feedback.Status.Completed">已完成</sys:String>
<sys:String x:Key="Setup.Feedback.Status.Declined">已拒绝</sys:String>
<sys:String x:Key="Setup.Feedback.Status.Duplicate">重复</sys:String>
<sys:String x:Key="Setup.Feedback.Status.Ignored">已忽略</sys:String>
<sys:String x:Key="Setup.Feedback.Status.Paused">暂停</sys:String>
<sys:String x:Key="Setup.Feedback.Status.Processing">正在处理</sys:String>
<sys:String x:Key="Setup.Feedback.Status.UpNext">在即</sys:String>
<sys:String x:Key="Setup.Feedback.Status.Waiting">等待</sys:String>
<sys:String x:Key="Setup.Feedback.Status.WaitingProcess">等待处理</sys:String>
<!-- Setup.GameLink -->
<sys:String x:Key="Setup.GameLink.CliDebugOutput">在日志中输出 Cli 信息以用于调试</sys:String>
<sys:String x:Key="Setup.GameLink.CliDebugOutput.ToolTip">在启动器日志中每 30s 输出一次 EasyTier Cli 信息以便于开发者和社区支持调查。&#xA;该选项仅用于调试目的,不推荐一直开启。</sys:String>
<sys:String x:Key="Setup.GameLink.EnableIPv6">允许使用 IPv6 通信</sys:String>
<sys:String x:Key="Setup.GameLink.EnableIPv6.ToolTip">IPv6 相比于 IPv4 更加容易进行 P2P 通信,能极大改善联机体验,除有特殊原因外不建议关闭。</sys:String>
<sys:String x:Key="Setup.GameLink.Error.ConfigChangeFailed">改变配置项失败</sys:String>
<sys:String x:Key="Setup.GameLink.Error.InitFailed">初始化联机页设置失败</sys:String>
<sys:String x:Key="Setup.GameLink.Error.NetworkTestFailed">获取网络测试结果失败。请查看错误详情。</sys:String>
<sys:String x:Key="Setup.GameLink.Initialized">已初始化联机页设置!</sys:String>
<sys:String x:Key="Setup.GameLink.LatencyFirstMode">选择最低延迟路径而不是最短路径</sys:String>
<sys:String x:Key="Setup.GameLink.LatencyFirstMode.ToolTip">启用后,EasyTier 将在通信时选择延迟最低的路径,而不是路由跳数最少的路径。&#xA;启用此选项在部分情况下能降低联机时的延迟,但可能会提高丢包率。</sys:String>
<sys:String x:Key="Setup.GameLink.PortGuessNat">对对称型 NAT 进行端口猜测</sys:String>
<sys:String x:Key="Setup.GameLink.PortGuessNat.ToolTip">让 EasyTier 尝试对对称型 NAT 进行端口猜测(生日悖论)。&#xA;这也许可以改善联机体验,但在部分情况下也可能导致一些问题。&#xA;一般保持开启即可。</sys:String>
<sys:String x:Key="Setup.GameLink.Protocol.Preference">协议偏好</sys:String>
<sys:String x:Key="Setup.GameLink.Protocol.Tcp">TCP</sys:String>
<sys:String x:Key="Setup.GameLink.Protocol.Tcp.ToolTip">让 EasyTier 优先使用 TCP 进行通信,TCP 通常能带来更好的稳定性</sys:String>
<sys:String x:Key="Setup.GameLink.Protocol.Udp">UDP</sys:String>
<sys:String x:Key="Setup.GameLink.Protocol.Udp.ToolTip">让 EasyTier 优先使用 UDP 进行通信,UDP 通常能带来更低的通信时延</sys:String>
<sys:String x:Key="Setup.GameLink.RestartRequired">修改此处的设置后,需要重新启动大厅以使设置生效。</sys:String>
<sys:String x:Key="Setup.GameLink.Username">用户名</sys:String>
<sys:String x:Key="Setup.GameLink.Username.ToolTip">PCL CE 会尽可能使用此处的用户名用于大厅信息展示。&#xA;若留空,则使用 Natayark ID 的用户名。&#xA;&#xA;部分情况下可能始终展示 Natayark ID 用户名,而非此处设置的名称。</sys:String>
<!-- Setup.GameLink.NetworkTest -->
<sys:String x:Key="Setup.GameLink.NetworkTest.Ipv6Status">IPv6: {0}</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.Ipv6Status.NotTested">IPv6: 尚未检测</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.NotTested">尚未检测</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.Start">开始测试</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.Supported">支持</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.TcpNatType">TCP NAT 类型: {0}</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.TcpNatType.NotTested">TCP NAT 类型: 尚未检测</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.Testing">正在测试</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.Title">网络测试</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.UdpNatType">UDP NAT 类型: {0}</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.UdpNatType.NotTested">UDP NAT 类型: 尚未检测</sys:String>
<sys:String x:Key="Setup.GameLink.NetworkTest.Unsupported">不支持</sys:String>
<!-- Setup.GameManage -->
<sys:String x:Key="Setup.GameManage.Error.InitFailed">初始化其他页设置失败</sys:String>
<sys:String x:Key="Setup.GameManage.Initialized">已初始化其他页设置!</sys:String>
<sys:String x:Key="Setup.GameManage.Source.File">文件下载源</sys:String>
<sys:String x:Key="Setup.GameManage.Source.PreferOfficialThenMirror">优先使用官方源,在加载缓慢时换用镜像源</sys:String>
<sys:String x:Key="Setup.GameManage.Source.Title">游戏资源获取行为</sys:String>
<sys:String x:Key="Setup.GameManage.Source.UseMirror">尽量使用镜像源</sys:String>
<sys:String x:Key="Setup.GameManage.Source.UseOfficial">尽量使用官方源</sys:String>
<sys:String x:Key="Setup.GameManage.Source.Version">版本列表源</sys:String>
<sys:String x:Key="Setup.GameManage.Source.Version.Mirror">尽量使用镜像源(可能缺少刚刚更新的版本)</sys:String>
<!-- Setup.GameManage.Accessibility -->
<sys:String x:Key="Setup.GameManage.Accessibility.AutoLauncherLanguage">自动设置为启动器语言</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.GameLanguage">游戏语言</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.GameUpdateNotice">游戏更新提示</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.ReadClipboard">识别跳转剪贴板中的社区资源链接</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.ReadClipboard.ToolTip">开启此选项后,PCL CE 将会读取你的剪贴板并识别其中的资源链接,并询问你是否要跳转到详情页面。</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.ReleaseNotice">正式版更新提示</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.ReleaseNotice.ToolTip">在 Minecraft 正式版更新时弹窗提示</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.SnapshotNotice">测试版更新提示</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.SnapshotNotice.ToolTip">在 Minecraft 测试版(快照、预发布版)更新时弹窗提示</sys:String>
<sys:String x:Key="Setup.GameManage.Accessibility.Title">辅助功能</sys:String>
<!-- Setup.GameManage.Community -->
<sys:String x:Key="Setup.GameManage.Community.AutoInstallDependencies">下载模组时自动检查并安装必需前置</sys:String>
<sys:String x:Key="Setup.GameManage.Community.AutoInstallDependencies.ToolTip">安装模组时,若检测到缺少必需前置,将列出前置列表供确认后自动下载安装(可在确认时取消)</sys:String>
<sys:String x:Key="Setup.GameManage.Community.FilenameFormat">文件名格式</sys:String>
<sys:String x:Key="Setup.GameManage.Community.FilenameFormat.Style0">【机械动力】create-1.21.1-6.0.4</sys:String>
<sys:String x:Key="Setup.GameManage.Community.FilenameFormat.Style1">[机械动力] create-1.21.1-6.0.4</sys:String>
<sys:String x:Key="Setup.GameManage.Community.FilenameFormat.Style2">机械动力-create-1.21.1-6.0.4</sys:String>
<sys:String x:Key="Setup.GameManage.Community.FilenameFormat.Style3">create-1.21.1-6.0.4-机械动力</sys:String>
<sys:String x:Key="Setup.GameManage.Community.FilenameFormat.Style4">create-1.21.1-6.0.4</sys:String>
<sys:String x:Key="Setup.GameManage.Community.FilenameFormat.ToolTip">下载社区资源时,其默认文件名的格式</sys:String>
<sys:String x:Key="Setup.GameManage.Community.HideQuilt">不显示 Quilt 加载器</sys:String>
<sys:String x:Key="Setup.GameManage.Community.HideQuilt.ToolTip">不在社区资源的加载器展示中显示 Quilt 加载器</sys:String>
<sys:String x:Key="Setup.GameManage.Community.ModManageStyle">模组管理样式</sys:String>
<sys:String x:Key="Setup.GameManage.Community.ModManageStyle.TitleFilename">标题显示文件名,详情显示译名</sys:String>
<sys:String x:Key="Setup.GameManage.Community.ModManageStyle.TitleTranslation">标题显示译名,详情显示文件名</sys:String>
<sys:String x:Key="Setup.GameManage.Community.ModManageStyle.ToolTip">在模组管理页面中,模组项的显示方式</sys:String>
<sys:String x:Key="Setup.GameManage.Community.QuickDownload">快速下载行为</sys:String>
<sys:String x:Key="Setup.GameManage.Community.QuickDownload.AlwaysAsk">总是询问</sys:String>
<sys:String x:Key="Setup.GameManage.Community.QuickDownload.AskInstance">询问并下载到选择的实例</sys:String>
<sys:String x:Key="Setup.GameManage.Community.QuickDownload.AskPath">询问并下载到一个路径</sys:String>
<sys:String x:Key="Setup.GameManage.Community.QuickDownload.CurrentInstance">下载到当前选中实例</sys:String>
<sys:String x:Key="Setup.GameManage.Community.QuickDownload.ToolTip">点击资源搜索结果卡片上的快速下载按钮时的行为。</sys:String>
<sys:String x:Key="Setup.GameManage.Community.Source">下载源</sys:String>
<sys:String x:Key="Setup.GameManage.Community.Source.PreferOfficialWithFallback">仅在官方源加载缓慢时改用镜像源</sys:String>
<sys:String x:Key="Setup.GameManage.Community.Title">社区资源获取行为</sys:String>
<!-- Setup.GameManage.Download -->
<sys:String x:Key="Setup.GameManage.Download.AutoSelectInstance">安装新实例后自动选定该实例</sys:String>
<sys:String x:Key="Setup.GameManage.Download.FixAuthlib">升级部分版本的 Authlib</sys:String>
<sys:String x:Key="Setup.GameManage.Download.FixAuthlib.ToolTip">部分 MC 版本(如 1.16.5)存在 Bug,可能导致使用离线验证时多人游戏被禁用。&#xA;PCL CE 可尝试升级这些版本的 Authlib 支持库以解决问题。&#xA;此设置仅对新安装的实例有效。</sys:String>
<sys:String x:Key="Setup.GameManage.Download.InstallBehavior">安装行为</sys:String>
<sys:String x:Key="Setup.GameManage.Download.SpeedLimit">速度限制</sys:String>
<sys:String x:Key="Setup.GameManage.Download.SpeedLimit.ToolTip">设置下载的速度上限,以避免在下载时导致其他需要联网的程序卡死</sys:String>
<sys:String x:Key="Setup.GameManage.Download.TargetFolder">目标文件夹</sys:String>
<sys:String x:Key="Setup.GameManage.Download.TargetFolder.Hint">请在 启动 → 实例选择 → 文件夹列表 中更改下载目标文件夹。&#xA;在某个文件夹或游戏实例上右键,即可选择打开对应文件夹。</sys:String>
<sys:String x:Key="Setup.GameManage.Download.Threads">最大线程数</sys:String>
<sys:String x:Key="Setup.GameManage.Download.Threads.TooManyWarning.Confirm">我知道了</sys:String>
<sys:String x:Key="Setup.GameManage.Download.Threads.TooManyWarning.Message">如果设置过多的下载线程,可能会导致下载时出现非常严重的卡顿。&#xA;一般设置 64 线程即可满足大多数下载需求,除非你知道你在干什么,否则不建议设置更多的线程数!</sys:String>
<sys:String x:Key="Setup.GameManage.Download.Threads.ToolTip">线程数越多,限速的文件下载越快,但过高的线程数可能会造成下载时非常严重的卡顿。&#xA;一般而言,64 线程已可以保证足够的下载速度。</sys:String>
<sys:String x:Key="Setup.GameManage.Download.Unlimited">无限制</sys:String>
<!-- Setup.Java -->
<sys:String x:Key="Setup.Java.Add">添加</sys:String>
<sys:String x:Key="Setup.Java.AddFailed">未能成功将 Java 加入列表中</sys:String>
<sys:String x:Key="Setup.Java.Added">已添加 Java</sys:String>
<sys:String x:Key="Setup.Java.AlreadyExists">Java 已经存在,不用再次添加……</sys:String>
<sys:String x:Key="Setup.Java.AutoSelect.Info">Java 选择自动挡,依据游戏需要自动选择合适的 Java</sys:String>
<sys:String x:Key="Setup.Java.AutoSelect.Title">自动选择</sys:String>
<sys:String x:Key="Setup.Java.DeselectBeforeDisable">请先取消选择此 Java 作为默认 Java 后再禁用</sys:String>
<sys:String x:Key="Setup.Java.Detail.ToolTip">详细信息</sys:String>
<sys:String x:Key="Setup.Java.Disable">禁用此 Java</sys:String>
<sys:String x:Key="Setup.Java.Enable">启用此 Java</sys:String>
<sys:String x:Key="Setup.Java.EnableBeforeSelect">请先启用此 Java 后再选择其作为默认 Java</sys:String>
<sys:String x:Key="Setup.Java.EnableFailed">调整 Java 启用状态失败</sys:String>
<sys:String x:Key="Setup.Java.Info.Format">类型: {0}&#xD;&#xA;版本: {1}&#xD;&#xA;架构: {2} ({3})&#xD;&#xA;发行商: {4}&#xD;&#xA;位置: {5}</sys:String>
<sys:String x:Key="Setup.Java.Info.Title">Java 信息</sys:String>
<sys:String x:Key="Setup.Java.Loading">正在加载 Java 列表</sys:String>
<sys:String x:Key="Setup.Java.SelectFile.Filter">Java 程序(java.exe)|java.exe</sys:String>
<sys:String x:Key="Setup.Java.SelectFile.Title">选择 Java 程序</sys:String>
<sys:String x:Key="Setup.Java.Unavailable">此 Java 不可用,请刷新列表</sys:String>
<!-- Setup.Language -->
<sys:String x:Key="Setup.Language.CardTitle">语言</sys:String>
<sys:String x:Key="Setup.Language.Changed">语言设置已更新</sys:String>
<sys:String x:Key="Setup.Language.FormatCulture">区域格式</sys:String>
<sys:String x:Key="Setup.Language.FormatCulture.Auto">跟随系统区域格式</sys:String>
<sys:String x:Key="Setup.Language.FormatCulture.FollowLanguage">同步界面语言</sys:String>
<sys:String x:Key="Setup.Language.Banner.TitlePrefix">让</sys:String>
<sys:String x:Key="Setup.Language.Banner.TitleSuffix">走向国际化。</sys:String>
<sys:String x:Key="Setup.Language.Banner.DescriptionLine1">在社区的共同助力下,PCL CE 正迈向国际化的新篇章。</sys:String>
<sys:String x:Key="Setup.Language.Banner.DescriptionLine2">欢迎向我们的 GitHub 仓库提交 Pull Request,与我们一同完善多语言支持,为 PCL CE 更好地服务全球用户的目标添砖加瓦。</sys:String>
<sys:String x:Key="Setup.Language.Banner.DescriptionLine3">感谢每一位参与翻译、校对与改进的贡献者,让 PCL CE 因你们而更加完善。</sys:String>
<sys:String x:Key="Setup.Language.Banner.SubmitPullRequest">提交 Pull Request</sys:String>
<sys:String x:Key="Setup.Language.Banner.ViewDeveloperDocs">查看开发文档</sys:String>
<sys:String x:Key="Setup.Language.Reset.Success">已初始化语言页设置!</sys:String>
<sys:String x:Key="Setup.Language.Title">语言</sys:String>
<sys:String x:Key="Setup.Language.UiLanguage">界面语言</sys:String>
<sys:String x:Key="Setup.Language.UiLanguage.Auto">跟随系统({0}</sys:String>
<!-- Setup.Launch -->
<sys:String x:Key="Setup.Launch.Error.ConfigReset">启动设置项存在异常,已被自动重置</sys:String>
<sys:String x:Key="Setup.Launch.Error.InitFailed">初始化启动设置失败</sys:String>
<sys:String x:Key="Setup.Launch.Error.LoadFailed">重载启动设置时出错</sys:String>
<sys:String x:Key="Setup.Launch.Initialized">已初始化启动设置!</sys:String>
<sys:String x:Key="Setup.Launch.SwitchToInstanceSetup">实例独立设置</sys:String>
<!-- Setup.Launch.Advanced -->
<sys:String x:Key="Setup.Launch.Advanced.DisableCrashAnalysis">禁用自动崩溃分析</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableCrashAnalysis.ToolTip">启用后,游戏崩溃时将不再自动弹出崩溃分析页面。</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableJlw">禁用 Java Launch Wrapper</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableJlw.Arm64Notice">在启动游戏时不使用 Java Wrapper 进行包装。&#xA;由于系统为 ARM64 架构,Java Wrapper 已被强制禁用。</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableJlw.Arm64ToolTip">在启动游戏时不使用 Java Wrapper 进行包装。&#xA;由于系统为 ARM64 架构,Java Wrapper 已被强制禁用。</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableJlw.ToolTip">PCL CE 会使用 Java Launch Wrapper 来修复老版本 Java 在中文路径下无法启动的问题。&#xA;如果这被反作弊拦截,或遇到其他问题,也可以将其关闭。</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableLF">禁用 LegacyFix</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableLF.ToolTip">PCL CE 会在启动 1.5.x 及以下版本时使用 LegacyFix 来改善老版本游戏体验和修复兼容性问题。</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.LockMemory">锁定内存分配(-Xms = -Xmx</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.LockMemory.ToolTip">启用后,启动器会额外追加 -Xms 参数并令其等于最大堆 -Xmx,从而固定堆大小。这会隐式禁用堆内存归还(Uncommit),减少操作系统取消映射/重新分配内存页带来的延迟抖动,也有利于 ZGC 的回收预测。代价是游戏全程会占用接近最大分配的内存。</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableLwjglUnsafeAgent">禁用 LWJGL Unsafe Agent</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.DisableLwjglUnsafeAgent.ToolTip">Minecraft 26.1 使用了 LWJGL 3.4.1LWJGL 3.4.1 使用了 FFM API 以在 JDK 25+ 上访问原生内存。由于 JDK 优化能力限制,某些情况下这会产生严重的性能问题。&#xA;PCL CE 会使用 LWJGL Unsafe Agent 修复这个问题,以解决可能存在的性能问题。&#xA;一般情况下没有禁用该选项的必要,但如果你遇到了额外问题,也可以尝试暂时禁用该选项以排查错误。&#xA;&#xA;该功能仅会在 LWJGL 版本为 3.4.1 时生效。</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.GameTail">游戏参数尾部</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.GameTail.ToolTip">文本框中的内容将会被直接拼合在启动参数的末尾。&#xA;例如,输入 --demo 则会以试玩模式启动游戏。&#xA;支持 Minecraft 实例 JSON 中的字符串替换标记,例如 ${library_directory}。</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.GpuPreference">要求 Java 使用高性能显卡</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.GpuPreference.ToolTip">自动在 Windows 设置 → 系统 → 屏幕 → 显示卡 中将 Java 改为使用高性能显卡。&#xA;如果电脑同时有集显和独显,这能确保 MC 使用独显进行渲染,以大幅提高性能。</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.JvmHead">JVM 参数头部</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.JvmHead.Restore">还原</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.JvmHead.ToolTip">启动 Minecraft 时使用的额外 JVM 参数,除非有确定把握,否则请不要修改。&#xA;支持 Minecraft 实例 JSON 中的字符串替换标记,例如 ${library_directory}。</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.MoreInInstanceSetup">你还可以在实例独立设置中找到更多高级启动选项。</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.PreLaunchCommand">启动前执行命令</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.PreLaunchCommand.ToolTip">在 MC 启动前执行特定命令或程序,语法与 Windows 的命令提示符一致。&#xA;&#xA;可以使用以下替换标记实现相对路径(路径均以 \ 结尾):&#xA; · {path}PCL CE 的 exe 文件所在的文件夹&#xA; · {minecraft}.minecraft 文件夹&#xA; · {verpath}:实例文件夹(.minecraft\versions\实例名\&#xA; · {verindie}:开启实例隔离时等同实例文件夹,未开启时等同 .minecraft 文件夹&#xA; · {java}:游戏运行时的 Java 文件夹&#xA;&#xA;除此之外,也支持以下替换标记:&#xA; · {user}:玩家名字&#xA; · {login}:玩家的登录方式&#xA; · {uuid}:玩家的 UUID&#xA; · {name}:游戏实例名&#xA; · {date}、{time}:当前的系统时间&#xA; · {version}:游戏对应的原版版本号&#xA;&#xA;例如:&#xA; · "{verpath}test.exe" :运行实例文件夹下的 test.exe 程序&#xA; · "{java}java.exe" -jar "{verpath}test.jar" :用 Java 运行实例文件夹下的 test.jar&#xA; · notepad "{verindie}options.txt" :使用记事本打开该实例的设置文件&#xA;&#xA;涉及路径的操作最好都打上双引号,以避免路径中的空格导致运行失败。&#xA;执行命令时,命令行所在的目录是当前的 .minecraft 文件夹。</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.PreLaunchCommand.Wait">等待命令执行完成后再继续启动</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.Renderer.DirectX12">DirectX12d3d12</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.Renderer.GameDefault">游戏默认</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.Renderer.Label">渲染器</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.Renderer.Software">软渲染(llvmpipe</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.Renderer.Vulkan">Vulkanzink</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.Renderer.Warning.Confirm">我知道我在做什么</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.Renderer.Warning.Message">修改此项会严重影响游戏的稳定性与性能。如果你不知道你在做什么,不要修改此选项!&#xA;你确定要继续修改吗?</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.Title">高级启动选项</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.UseJavaExe">使用 java.exe 而不是 javaw.exe</sys:String>
<sys:String x:Key="Setup.Launch.Advanced.UseJavaExe.ToolTip">如果遇到游戏窗口长时间不出现等故障可尝试启用此项,PCL CE 将使用 java.exe 来运行游戏,这样会让游戏进程拥有标准输入/输出流,以尽可能避免打印日志时出现问题。&#xA;&#xA;取决于不同 Java 发行版的行为,java.exe 可能无法使用 HDR 颜色等依赖操作系统支持的特性。因此,如果正常使用没有问题,请勿启用该选项。</sys:String>
<!-- Setup.Launch.Memory -->
<sys:String x:Key="Setup.Launch.Memory.Auto">自动配置</sys:String>
<sys:String x:Key="Setup.Launch.Memory.Auto.ToolTip">根据安装的模组量与电脑剩余内存,动态调整为游戏分配的内存</sys:String>
<sys:String x:Key="Setup.Launch.Memory.AvailableSuffix">可用 {0} GiB</sys:String>
<sys:String x:Key="Setup.Launch.Memory.GameAllocation">游戏分配</sys:String>
<sys:String x:Key="Setup.Launch.Memory.Title">游戏内存</sys:String>
<sys:String x:Key="Setup.Launch.Memory.UsedTotal">已使用内存 / 已安装内存</sys:String>
<sys:String x:Key="Setup.Launch.Memory.Warning.Java32Bit">32 位 Java 只能分配最多 1 GiB 的内存。强烈建议换用 64 位的 Java</sys:String>
<sys:String x:Key="Setup.Launch.Memory.Warning.TooMuch">你给游戏分配的内存过多,这可能引发游戏崩溃。建议优先考虑「自动分配」选项!</sys:String>
<!-- Setup.Launch.Options -->
<sys:String x:Key="Setup.Launch.Options.CustomInfo.Label">自定义信息</sys:String>
<sys:String x:Key="Setup.Launch.Options.CustomInfo.ToolTip">该信息会显示在游戏主界面的左下角,与 F3 调试页面的左上角</sys:String>
<sys:String x:Key="Setup.Launch.Options.IpStack.Ipv4">IPv4 优先</sys:String>
<sys:String x:Key="Setup.Launch.Options.IpStack.Ipv6">IPv6 优先</sys:String>
<sys:String x:Key="Setup.Launch.Options.IpStack.JavaDefault">Java 默认</sys:String>
<sys:String x:Key="Setup.Launch.Options.IpStack.Label">IP 协议偏好</sys:String>
<sys:String x:Key="Setup.Launch.Options.IpStack.ToolTip">通过设置 Java 虚拟机参数来设置 MC 的 IP 协议版本偏好。&#xA;一般建议设置为 "IPv4 优先",这能确保 PCL CE 大厅正常工作。&#xA;如果有手动 IPv6 联机等需要,可以选择其他选项。&#xA;&#xA;IPv4 优先:-Djava.net.preferIPv4Stack=true&#xA;默认:不添加额外参数&#xA;IPv6 优先:-Djava.net.preferIPv6Stack=true</sys:String>
<sys:String x:Key="Setup.Launch.Options.MsAuth.DeviceCode">设备代码流</sys:String>
<sys:String x:Key="Setup.Launch.Options.MsAuth.Label">正版验证方式</sys:String>
<sys:String x:Key="Setup.Launch.Options.MsAuth.ToolTip">Web 账户管理器的正版验证流程更为简单,而设备代码流允许你在任意设备上打开网页进行验证。&#xA;如果使用 Web 账户管理器登录遇到问题,可以尝试使用设备代码流。&#xA;如果正在公共计算机上临时登录账号,推荐使用设备代码流。&#xA;&#xA;由于部分依赖组件存在较多问题,该选项暂时强制指定为设备代码流。</sys:String>
<sys:String x:Key="Setup.Launch.Options.MsAuth.WebManager">Web 账户管理器(暂时强制设备代码流)</sys:String>
<sys:String x:Key="Setup.Launch.Options.Priority.AboveNormal">高(优先保证游戏运行,游戏性能更佳,但可能造成其他程序卡顿)</sys:String>
<sys:String x:Key="Setup.Launch.Options.Priority.BelowNormal">低(优先保证其他程序运行,但可能让游戏卡顿,适合挂机时使用)</sys:String>
<sys:String x:Key="Setup.Launch.Options.Priority.High">极高(谨慎使用,可能严重影响其他程序运行)</sys:String>
<sys:String x:Key="Setup.Launch.Options.Priority.Label">进程优先级</sys:String>
<sys:String x:Key="Setup.Launch.Options.Priority.Normal">中(平衡)</sys:String>
<sys:String x:Key="Setup.Launch.Options.Priority.RealTime">实时(使游戏以最高优先级运行,谨慎使用,可能严重影响系统运行)</sys:String>
<sys:String x:Key="Setup.Launch.Options.Title">启动选项</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowSize.Custom">自定义尺寸</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowSize.CustomHeightBug.ToolTip">Forge 1.12.2 等个别版本存在 Bug,会导致高度设定无效</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowSize.Fullscreen">全屏</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowSize.Label">窗口大小</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowSize.Maximized">最大化</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowSize.ModernUiWarning">注意:如果你的实例安装了 Modern UI 模组,那么这里的设置将会失效</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowSize.SameAsLauncher">与启动器尺寸一致</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowTitle.Label">游戏窗口标题</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowTitle.SampleFormat">{name} | 玩家 : {user} | 使用 {login} 登录</sys:String>
<sys:String x:Key="Setup.Launch.Options.WindowTitle.ToolTip">自定义游戏窗口的标题,若留空则不进行更改。&#xA;支持以下替换标记:&#xA; · {user}:玩家名字&#xA; · {login}:玩家的登录方式&#xA; · {name}:游戏实例名&#xA; · {date}、{time}:当前的系统时间&#xA; · {version}:游戏对应的原版版本号&#xA;注意:如果你的实例安装了 Modern UI 模组,那么这里的设置将会失效</sys:String>
<!-- Setup.Launch.Options.InstanceIsolation -->
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.All">隔离所有实例</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.All.ToolTip">不同实例之间的存档、模组、资源包等均不互通。&#xA;这会导致不同原版实例间的存档不能共用,所以一般不推荐。</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.DefaultPolicyHint">默认策略只会对今后新安装的实例生效。&#xA;已有实例的隔离策略需要在它的设置中调整。</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.Label">默认实例隔离</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.ModdedAndNonRelease">隔离可安装模组的实例与非正式版</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.ModdedOnly">隔离可安装模组的实例</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.ModdedOnly.ToolTip">可安装模组的实例(例如 Forge、Fabric)均互相独立以避免模组冲突,其他实例(例如原版)则不会被隔离。</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.NonRelease">隔离非正式版</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.NonRelease.ToolTip">将 Minecraft 快照、预发布版、远古版本、愚人节版本与其他实例进行隔离</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.None.ToolTip">所有实例均使用同一档案,存档、模组、资源包等均为公用。&#xA;若存在多个安装了模组的实例,可能会导致模组冲突。</sys:String>
<sys:String x:Key="Setup.Launch.Options.InstanceIsolation.ToolTip">当安装新实例时,据此自动设置新实例的隔离选项。&#xA;若想调整已有实例的隔离策略,请前往它的实例设置。</sys:String>
<!-- Setup.Launch.Options.Visibility -->
<sys:String x:Key="Setup.Launch.Options.Visibility.CloseImmediately">游戏启动后立即关闭</sys:String>
<sys:String x:Key="Setup.Launch.Options.Visibility.CloseImmediately.Warning.Continue">继续</sys:String>
<sys:String x:Key="Setup.Launch.Options.Visibility.CloseImmediately.Warning.Message">若在游戏启动后立即关闭启动器,崩溃检测、更改游戏标题等功能将失效。&#xA;如果想保留这些功能,可以选择让启动器在游戏启动后隐藏,游戏退出后自动关闭。</sys:String>
<sys:String x:Key="Setup.Launch.Options.Visibility.CloseImmediately.Warning.Title">提醒</sys:String>
<sys:String x:Key="Setup.Launch.Options.Visibility.HideAndCloseOnExit">游戏启动后隐藏,游戏退出后自动关闭</sys:String>
<sys:String x:Key="Setup.Launch.Options.Visibility.HideAndReopenOnExit">游戏启动后隐藏,游戏退出后重新打开</sys:String>
<sys:String x:Key="Setup.Launch.Options.Visibility.KeepUnchanged">游戏启动后仍保持不变</sys:String>
<sys:String x:Key="Setup.Launch.Options.Visibility.Label">启动器可见性</sys:String>
<sys:String x:Key="Setup.Launch.Options.Visibility.Minimize">游戏启动后最小化</sys:String>
<!-- Setup.Left -->
<sys:String x:Key="Setup.Left.Category.About">关于</sys:String>
<sys:String x:Key="Setup.Left.Category.Game">游戏</sys:String>
<sys:String x:Key="Setup.Left.Category.Launcher">启动器</sys:String>
<sys:String x:Key="Setup.Left.Category.Tools">工具</sys:String>
<sys:String x:Key="Setup.Left.Feedback.Message">在提交新反馈前,建议先搜索反馈列表,以避免重复提交。&#xA;如果无法打开该网页,请尝试使用加速器或 VPN。</sys:String>
<sys:String x:Key="Setup.Left.Feedback.SubmitNew">提交新反馈</sys:String>
<sys:String x:Key="Setup.Left.Feedback.Title">反馈</sys:String>
<sys:String x:Key="Setup.Left.Feedback.ViewList">查看反馈列表</sys:String>
<sys:String x:Key="Setup.Left.Refreshing">正在刷新……</sys:String>
<sys:String x:Key="Setup.Left.Reset.GameLink.Message">是否要初始化 工具-联机 页面的所有设置?该操作不可撤销。</sys:String>
<sys:String x:Key="Setup.Left.Reset.GameManage.Message">是否要初始化 游戏-管理 页面的所有设置?该操作不可撤销。</sys:String>
<sys:String x:Key="Setup.Left.Reset.Language.Message">是否要初始化 启动器-语言 页面的所有设置?该操作不可撤销。</sys:String>
<sys:String x:Key="Setup.Left.Reset.Launch.Message">是否要初始化 游戏-启动 页面的所有设置?该操作不可撤销。</sys:String>
<sys:String x:Key="Setup.Left.Reset.Misc.Message">是否要初始化 启动器-杂项 页面的所有设置?该操作不可撤销。</sys:String>
<sys:String x:Key="Setup.Left.Reset.Title">初始化确认</sys:String>
<sys:String x:Key="Setup.Left.Reset.Ui.Message">是否要初始化 启动器-个性化 页面的所有设置?该操作不可撤销。&#xA;(背景图片与音乐、主页等外部文件不会被删除)</sys:String>
<!-- Setup.Left.Item -->
<sys:String x:Key="Setup.Left.Item.About">软件信息</sys:String>
<sys:String x:Key="Setup.Left.Item.Feedback">反馈</sys:String>
<sys:String x:Key="Setup.Left.Item.GameLink">联机</sys:String>
<sys:String x:Key="Setup.Left.Item.GameManage">管理</sys:String>
<sys:String x:Key="Setup.Left.Item.Java">Java</sys:String>
<sys:String x:Key="Setup.Left.Item.Launch">启动</sys:String>
<sys:String x:Key="Setup.Left.Item.Log">查看日志</sys:String>
<sys:String x:Key="Setup.Left.Item.Misc">杂项</sys:String>
<sys:String x:Key="Setup.Left.Item.Ui">个性化</sys:String>
<sys:String x:Key="Setup.Left.Item.Update">软件更新</sys:String>
<!-- Setup.Log -->
<sys:String x:Key="Setup.Log.AllLogs">所有日志</sys:String>
<sys:String x:Key="Setup.Log.Clear.Confirm.Message">是否删除所有历史日志?</sys:String>
<sys:String x:Key="Setup.Log.Clear.Confirm.Title">清理历史日志</sys:String>
<sys:String x:Key="Setup.Log.Clear.Success">清理日志文件成功!</sys:String>
<sys:String x:Key="Setup.Log.ClearHistory">清理历史日志</sys:String>
<sys:String x:Key="Setup.Log.CurrentSuffix">{0} (当前)</sys:String>
<sys:String x:Key="Setup.Log.Export">导出日志</sys:String>
<sys:String x:Key="Setup.Log.ExportAll">导出全部日志</sys:String>
<sys:String x:Key="Setup.Log.ExportFailed">日志保存失败</sys:String>
<sys:String x:Key="Setup.Log.ExportFilter">PCL CE 日志压缩包|*.zip</sys:String>
<sys:String x:Key="Setup.Log.ExportSaveTitle">导出日志文件</sys:String>
<sys:String x:Key="Setup.Log.ExportSuccess">日志保存成功!</sys:String>
<sys:String x:Key="Setup.Log.OpenDirectory">打开日志目录</sys:String>
<sys:String x:Key="Setup.Log.Operations">日志操作</sys:String>
<sys:String x:Key="Setup.Log.TempStored">临时存储的日志 ({0})</sys:String>
<sys:String x:Key="Setup.Log.TempUnoutput">临时存储的未输出日志</sys:String>
<!-- Setup.Misc -->
<sys:String x:Key="Setup.Misc.Error.InitFailed">初始化启动器-杂项页设置失败</sys:String>
<sys:String x:Key="Setup.Misc.HardwareAcceleration.RestartNotice">此项变更将在重启 PCL CE 后生效</sys:String>
<sys:String x:Key="Setup.Misc.Initialized">已初始化杂项页设置!</sys:String>
<!-- Setup.Misc.Debug -->
<sys:String x:Key="Setup.Misc.Debug.AnimationSpeed">动画速度</sys:String>
<sys:String x:Key="Setup.Misc.Debug.Delay">添加延迟</sys:String>
<sys:String x:Key="Setup.Misc.Debug.Delay.ToolTip">在各个环节添加随机的延迟,拖慢加载速度,以测试部分功能是否正常运行。&#xA;这会严重影响启动器运行,若无特殊需要不建议开启。</sys:String>
<sys:String x:Key="Setup.Misc.Debug.Mode">调试模式</sys:String>
<sys:String x:Key="Setup.Misc.Debug.Mode.Hint">部分调试信息将在刷新或启动器重启后切换显示!</sys:String>
<sys:String x:Key="Setup.Misc.Debug.Mode.ToolTip">显示调试信息与更多错误信息。&#xA;这会导致启动器性能略有下降,若无特殊需要不建议开启。</sys:String>
<sys:String x:Key="Setup.Misc.Debug.SkipCopy">禁止在下载时从其他文件夹复制文件</sys:String>
<sys:String x:Key="Setup.Misc.Debug.SkipCopy.ToolTip">在下载时不直接复制其他 MC 文件夹中已经存在的相同文件,而是重新下载文件。&#xA;只建议在测试下载速度时开启。</sys:String>
<sys:String x:Key="Setup.Misc.Debug.Title">调试选项</sys:String>
<!-- Setup.Misc.Network -->
<sys:String x:Key="Setup.Misc.Network.DoH">使用 DoH 解析地址</sys:String>
<sys:String x:Key="Setup.Misc.Network.DoH.ToolTip">使用 DoH 解析地址可以获取更加优质的 IP 结果,提高启动器的网络访问水平。如果您已部署 DoH 解析服务,可以关闭此功能。</sys:String>
<sys:String x:Key="Setup.Misc.Network.HttpProxy">HTTP 代理</sys:String>
<sys:String x:Key="Setup.Misc.Network.Title">网络</sys:String>
<!-- Setup.Misc.Network.Proxy -->
<sys:String x:Key="Setup.Misc.Network.Proxy.Apply">应用代理信息</sys:String>
<sys:String x:Key="Setup.Misc.Network.Proxy.Custom">自定义代理</sys:String>
<sys:String x:Key="Setup.Misc.Network.Proxy.InvalidWarning">不正确的代理服务器地址会导致无法连接网络,因使用代理导致的网络问题不予处理。&#xA;请勿填写不信任的代理服务器地址,这可能会导致你的账号信息泄露,甚至账号被盗。</sys:String>
<sys:String x:Key="Setup.Misc.Network.Proxy.None">不使用代理</sys:String>
<sys:String x:Key="Setup.Misc.Network.Proxy.Optional">如有</sys:String>
<sys:String x:Key="Setup.Misc.Network.Proxy.Password">密码</sys:String>
<sys:String x:Key="Setup.Misc.Network.Proxy.System">使用系统代理</sys:String>
<sys:String x:Key="Setup.Misc.Network.Proxy.UntrustedWarning">请勿填写不信任的代理地址</sys:String>
<sys:String x:Key="Setup.Misc.Network.Proxy.Username">账号</sys:String>
<!-- Setup.Misc.System -->
<sys:String x:Key="Setup.Misc.System.Announcement">启动器公告</sys:String>
<sys:String x:Key="Setup.Misc.System.Announcement.All">显示所有公告</sys:String>
<sys:String x:Key="Setup.Misc.System.Announcement.All.ToolTip">显示包括启动器功能调查、活动等在内的全部公告</sys:String>
<sys:String x:Key="Setup.Misc.System.Announcement.Disabled">关闭所有公告</sys:String>
<sys:String x:Key="Setup.Misc.System.Announcement.Disabled.Warning.Confirm">我知道我在做什么</sys:String>
<sys:String x:Key="Setup.Misc.System.Announcement.Disabled.Warning.Message">若选择此项,即使在将来出现严重问题时,你也无法获取相关通知。&#xA;例如,如果发现某个版本游戏存在严重 Bug,你可能就会因为无法得到通知而导致无法预知的后果。&#xA;&#xA;一般选择 仅在有重要通知时显示公告 就可以让你尽量不受打扰了。&#xA;除非你在制作服务器整合包,或时常手动更新启动器,否则极度不推荐选择此项!</sys:String>
<sys:String x:Key="Setup.Misc.System.Announcement.ImportantOnly">仅在有重要通知时显示公告</sys:String>
<sys:String x:Key="Setup.Misc.System.DisableHardwareAcceleration">禁用硬件加速</sys:String>
<sys:String x:Key="Setup.Misc.System.ExportSettings">导出设置</sys:String>
<sys:String x:Key="Setup.Misc.System.ExportSettings.Filter">PCL CE 配置文件(*.json)|*.json</sys:String>
<sys:String x:Key="Setup.Misc.System.ExportSettings.SaveTitle">选择保存位置</sys:String>
<sys:String x:Key="Setup.Misc.System.ExportSettings.Success">配置导出成功!</sys:String>
<sys:String x:Key="Setup.Misc.System.ImportSettings">导入设置</sys:String>
<sys:String x:Key="Setup.Misc.System.ImportSettings.SelectTitle">选择配置文件</sys:String>
<sys:String x:Key="Setup.Misc.System.ImportSettings.Success.Message">配置导入成功!请重启 PCL CE 以应用配置……</sys:String>
<sys:String x:Key="Setup.Misc.System.ImportSettings.Success.Restart">重启</sys:String>
<sys:String x:Key="Setup.Misc.System.MaxFps">最高动画帧率</sys:String>
<sys:String x:Key="Setup.Misc.System.MaxFps.ToolTip">如果你禁用了硬件加速,那么建议你调低最高动画帧率以防止过多的资源占用</sys:String>
<sys:String x:Key="Setup.Misc.System.MaxLogLines">实时日志行数</sys:String>
<sys:String x:Key="Setup.Misc.System.MaxLogLines.ToolTip">实时日志功能中最大显示行数,超过该值会自动删除旧的日志。</sys:String>
<sys:String x:Key="Setup.Misc.System.MaxLogLines.Unlimited">无限制</sys:String>
<sys:String x:Key="Setup.Misc.System.StopUsingPclCe">停止使用 PCL CE</sys:String>
<sys:String x:Key="Setup.Misc.System.StopUsingPclCe.ContinueAndRemove">继续并清理实例文件夹</sys:String>
<sys:String x:Key="Setup.Misc.System.StopUsingPclCe.Message.Final">你确定要继续操作吗?这是最后警告!&#xA;与 PCL CE 有关的一切数据将被永久消除!(真的很久!)</sys:String>
<sys:String x:Key="Setup.Misc.System.StopUsingPclCe.Message">即将删除此电脑上与 PCL CE 有关的全部配置、缓存、已保存的账户信息及其他相关数据,并撤销已接受的许可协议。完成后,软件将自动关闭。&#xA;&#xA;但请注意,以下内容无法自动删除或注销:&#xA;- 此电脑上其他 PCL CE 实例的本地数据&#xA;- Natayark 账户&#xA;&#xA;如需同时删除 Minecraft 实例中的 PCL CE 配置文件,请选择“继续并清理实例文件夹”,这不会影响你的游戏配置、模组、存档,以及其他数据。&#xA;&#xA;此操作无法撤销。您确定要继续吗?</sys:String>
<sys:String x:Key="Setup.Misc.System.StopUsingPclCe.Title">停止使用 PCL CE</sys:String>
<sys:String x:Key="Setup.Misc.System.Telemetry">启用遥测数据收集</sys:String>
<sys:String x:Key="Setup.Misc.System.Telemetry.ToolTip">启用此功能后,启动器将会收集并上报错误与设备环境信息,这可以帮助开发者修复潜在的问题、更好的进行规划和开发。&#xA;若启用此功能,我们将会收集以下信息:启动器内出现的错误,启动器版本信息与识别码,使用的 Windows 系统版本与架构,已安装的物理内存大小,NAT 与 IPv6 支持情况,是否使用过原版 PCL、HMCL 或 BakaXL。&#xA;&#xA;这些数据均不与你关联,我们也绝不会向第三方出售数据。不启用此功能不会影响你使用其他功能,但可能会影响开发者修复潜在 Bug。&#xA;&#xA;此项更改将在重新启动启动器后生效。</sys:String>
<sys:String x:Key="Setup.Misc.System.Title">系统</sys:String>
<!-- Setup.Ui -->
<sys:String x:Key="Setup.Ui.Error.BackgroundVideoUnsupported">刷新背景内容失败。该视频文件可能并非 H.264(AVC)格式。请使用视频转码工具将其转码为 H.264(AVC)后再试。</sys:String>
<sys:String x:Key="Setup.Ui.Error.ConfigReset">个性化设置项存在异常,已被自动重置</sys:String>
<sys:String x:Key="Setup.Ui.Error.InitFailed">初始化个性化设置失败</sys:String>
<sys:String x:Key="Setup.Ui.Error.LoadFailed">重载个性化设置时出错</sys:String>
<sys:String x:Key="Setup.Ui.Error.OperationFailed">界面设置操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Setup.Ui.Error.TitleImageInvalidFormat">更改标题栏图片失败。该图片文件可能并非标准格式。请使用画图打开并重新保存该文件后再试。</sys:String>
<sys:String x:Key="Setup.Ui.Error.TitleImageResizeInvalidFormat">调整标题栏图片失败。该图片文件可能并非标准格式。请使用画图打开并重新保存该文件后再试。</sys:String>
<sys:String x:Key="Setup.Ui.Font.Global">全局</sys:String>
<sys:String x:Key="Setup.Ui.Font.Global.ToolTip">全局默认字体</sys:String>
<sys:String x:Key="Setup.Ui.Font.Motd">MOTD</sys:String>
<sys:String x:Key="Setup.Ui.Font.Motd.ToolTip">格式化代码文本字体,用于服务器状态信息等内容</sys:String>
<sys:String x:Key="Setup.Ui.Font.Title">字体</sys:String>
<sys:String x:Key="Setup.Ui.ImageFile.Filter">常用图片文件(*.png;*.jpg;*.gif;*.webp)|*.png;*.jpg;*.gif;*.webp</sys:String>
<sys:String x:Key="Setup.Ui.ImageFile.SelectTitle">选择图片</sys:String>
<sys:String x:Key="Setup.Ui.Initialized">已初始化个性化设置</sys:String>
<sys:String x:Key="Setup.Ui.Slider.Pixel">{0} 像素</sys:String>
<!-- Setup.Ui.Background -->
<sys:String x:Key="Setup.Ui.Background.Blur">背景模糊</sys:String>
<sys:String x:Key="Setup.Ui.Background.Blur.ToolTip">较强的模糊会导致启动器严重掉帧,请谨慎使用。&#xD;不建议在制作整合包时开启模糊,以确保 PCL CE 在低性能电脑上可以流畅运行。</sys:String>
<sys:String x:Key="Setup.Ui.Background.Clear">清空背景内容</sys:String>
<sys:String x:Key="Setup.Ui.Background.Clear.Confirm.Message">即将删除背景内容文件夹中的所有文件。&#xA;此操作不可撤销,是否确定?</sys:String>
<sys:String x:Key="Setup.Ui.Background.Clear.Success">背景内容已清空!</sys:String>
<sys:String x:Key="Setup.Ui.Background.Cleared">背景内容已清除!</sys:String>
<sys:String x:Key="Setup.Ui.Background.ColorOverlay">叠加彩色背景</sys:String>
<sys:String x:Key="Setup.Ui.Background.ContentAdapt">内容自适应方式</sys:String>
<sys:String x:Key="Setup.Ui.Background.ImageLoadFailed">图片加载失败,尝试将文件作为视频播放:{0}</sys:String>
<sys:String x:Key="Setup.Ui.Background.NoAvailableContent">未检测到可用背景内容!</sys:String>
<sys:String x:Key="Setup.Ui.Background.OpenFolder.ToolTip">将背景图片和视频放到该文件夹,PCL CE 将会在每次打开时随机加载其中的一张图片或视频&#xA;为提高播放时的流畅度,视频推荐使用H264格式编码</sys:String>
<sys:String x:Key="Setup.Ui.Background.PauseVideo">游戏启动后暂停视频背景,游戏退出后重新播放</sys:String>
<sys:String x:Key="Setup.Ui.Background.PauseVideo.ToolTip">若启用此项设置,游戏时将无法刷新背景</sys:String>
<sys:String x:Key="Setup.Ui.Background.Refresh">刷新背景内容</sys:String>
<sys:String x:Key="Setup.Ui.Background.Refresh.Success">背景内容已刷新:{0}</sys:String>
<sys:String x:Key="Setup.Ui.Background.Refresh.ToolTip">随机加载文件夹内的一张背景图片或视频&#xA;为提高播放时的流畅度,视频推荐使用H264格式编码</sys:String>
<sys:String x:Key="Setup.Ui.Background.Title">背景内容</sys:String>
<sys:String x:Key="Setup.Ui.Background.TitleDefault">背景图片/视频</sys:String>
<sys:String x:Key="Setup.Ui.Background.TitleWithCount">背景图片/视频({0} 张)</sys:String>
<!-- Setup.Ui.Background.Suit -->
<sys:String x:Key="Setup.Ui.Background.Suit.BottomLeft">居于左下</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.BottomRight">居于右下</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.Center">居中</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.Fit">适应</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.Fit.ToolTip">拉伸内容并保持长宽比不变,直至填满整个窗口</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.Smart">智能</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.Smart.ToolTip">当内容较小时选择平铺,较大时选择适应</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.Stretch">拉伸</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.Stretch.ToolTip">拉伸内容使其恰好填满整个窗口,很可能会导致长宽比的改变</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.Tile">平铺</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.TopLeft">居于左上</sys:String>
<sys:String x:Key="Setup.Ui.Background.Suit.TopRight">居于右上</sys:String>
<!-- Setup.Ui.Basic -->
<sys:String x:Key="Setup.Ui.Basic.AdvancedMaterial">高级材质</sys:String>
<sys:String x:Key="Setup.Ui.Basic.AdvancedMaterial.ToolTip">启用此功能后会在部分透明区域(例如卡片)实现模糊效果。&#xA;请注意,该功能会消耗大量 GPU 算力,并导致卡顿!</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlueHint">蓝色,蓝色,还是蓝色!想要逃离蓝色困境?请使用官方快照版!</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurMethod">模糊方法</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurMethod.Box">方框模糊</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurMethod.Box.ToolTip">对每个像素周围较小范围的像素使用线性均值采样,速度较快,但容易产生色块。&#xA;该模糊容易丢失细节信息,但性能较好,若设备性能不佳可选择使用。</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurMethod.Gaussian">高斯模糊</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurMethod.Gaussian.ToolTip">对每个像素周围较大范围的像素使用高斯卷积算法的带权重均值采样,速度较慢。&#xA;该模糊质量较高,对细节的处理较为细致,推荐性能较好的设备使用。</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurMethod.ToolTip">设定模糊方法。</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurRadius">模糊半径</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurRadius.ToolTip">设定模糊效果的采样半径,值越大模糊效果越强。</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurSamplingRate">采样率</sys:String>
<sys:String x:Key="Setup.Ui.Basic.BlurSamplingRate.ToolTip">设定模糊效果的采样率,值越大模糊效果越细致。&#xA;若设备性能不佳,可尝试降低该值以获得更好的性能。</sys:String>
<sys:String x:Key="Setup.Ui.Basic.GetSnapshot">获取官方快照版</sys:String>
<sys:String x:Key="Setup.Ui.Basic.LockWindow">锁定启动器大小</sys:String>
<sys:String x:Key="Setup.Ui.Basic.Opacity">不透明度</sys:String>
<sys:String x:Key="Setup.Ui.Basic.Opacity2">不透明度</sys:String>
<sys:String x:Key="Setup.Ui.Basic.ShowLogo">打开启动器时显示 PCL CE 图标</sys:String>
<sys:String x:Key="Setup.Ui.Basic.ShowTriviaHint">在启动游戏时显示"你知道吗"</sys:String>
<sys:String x:Key="Setup.Ui.Basic.Title">基础</sys:String>
<!-- Setup.Ui.FeatureHide -->
<sys:String x:Key="Setup.Ui.FeatureHide.Description">你可以隐藏不需要的页面或关闭特定功能。在任意界面按 F12 可以暂时显示被隐藏的功能。</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.MainPage">主页面</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.SpecificFeature">特定功能</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.SubInstance">子页面 实例设置</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.SubSetup">子页面 设置</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.SubTools">子页面 工具</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.TemporaryHint">按 F12 即可暂时关闭功能隐藏设置。千万别忘了,要不然设置就改不回来了……</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Title">功能隐藏</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.TitleTemporarilyDisabled">功能隐藏(已暂时关闭,按 F12 以重新启用)</sys:String>
<!-- Setup.Ui.FeatureHide.Item -->
<sys:String x:Key="Setup.Ui.FeatureHide.Item.About">关于</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Download">下载</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Edit">修改</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Export">导出</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.FeatureHide">功能隐藏</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.FeatureHide.ToolTip">仅隐藏本选项卡,设置的禁用内容依然有效。&#xA;可以在保留个性化设置页面的情况下避免隐藏设置被修改。</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Feedback">反馈</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.GameLink">联机</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.GameManage">管理</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.InstanceSelect">实例管理</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.InstanceSelect.ToolTip">隐藏启动按钮下方的实例选择与实例设置入口。</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Java">Java</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Language">语言</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Launch">启动</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Lobby">联机</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Log">查看日志</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Misc">杂项</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Mod">模组</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.ModUpdate">模组更新</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.ModUpdate.ToolTip">禁止在模组管理页面更新模组。</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.ResourcePack">资源包</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Save">存档</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Schematic">投影原理图</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Screenshot">截图</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Server">服务器</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Settings">设置</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Shader">光影包</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Toolbox">百宝箱</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Tools">工具</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Ui">个性化</sys:String>
<sys:String x:Key="Setup.Ui.FeatureHide.Item.Update">软件更新</sys:String>
<!-- Setup.Ui.Homepage -->
<sys:String x:Key="Setup.Ui.Homepage.Blank">空白</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.DownloadUrl">下载地址</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.GetMore">获取更多主页</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.GoToMarket">前往主页市场</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.HintReferToModSetup">内容见 ModSetup</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.LocalFile">读取本地文件</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.LocalFile.Hint">从 PCL 文件夹下的 Custom.xaml 读取主页内容。&#xA;你可以手动编辑该文件,向主页添加文本、图片、常用网站、快捷启动等功能。</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.NetUpdate">联网更新</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.NetUpdate.Hint">从指定网址联网获取主页内容。服主也可以用于动态更新服务器公告。&#xA;如果你制作了稳定运行的联网主页,可以点击这条提示投稿,若合格即可加入预设!</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.NetWarning">请谨慎使用陌生人提供的主页,恶意代码可能会造成损害!</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.PresetLabel">主页预设</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Refresh">刷新主页</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Refresh.Success">已刷新主页!</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Title">主页</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Tutorial">查看教程</sys:String>
<!-- Setup.Ui.Homepage.Preset -->
<sys:String x:Key="Setup.Ui.Homepage.Preset">预设</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.DailyModpack">每日整合包推荐(作者:wkea</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.EchoCave">回声洞 (已移除)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.GitHub">PCL GitHub 仪表盘(作者:Deep-Dark-Forest</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.Magazine">杂志主页(作者:CreeperIsASpy</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.McNews">Minecraft 新闻(作者:最亮的信标)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.McOfficialFeed">Minecraft 官方信息流</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.McSkin">Minecraft 皮肤推荐(作者:wkea</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.McUpdateSummary">Minecraft 更新摘要(作者:pynickle,部分由 AI 生成)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.NewsToday">今日新闻热点(作者:EYicheng</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.MinecraftKnowledge">Minecraft 芝士站(作者:awaEric233</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.ModpackRecommendation">整合包推荐引擎(作者:CHMYGZ</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.Bangumi">Bangumi 番剧主页(作者:wzyaeu</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.OpenBmclapi">OpenBMCLAPI 仪表盘 Lite(作者:Silverteal、Mxmilu666</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.OpenMcim">OpenMCIM 仪表盘(作者:SALTWOOD</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.PclAnnouncement">PCL CE 公告栏</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.PclManual">PCL 新功能说明书(作者:WForst-Breeze</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.PclMarket">PCL 主页市场(作者:凌云)</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.PclNews">PCL 新闻速报(作者:Joker2184</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.SimpleHomepage">简单主页(作者:MFn233</sys:String>
<sys:String x:Key="Setup.Ui.Homepage.Preset.Trivia">你知道吗?</sys:String>
<!-- Setup.Ui.Logo -->
<sys:String x:Key="Setup.Ui.Logo.ChangeImage">更改图片</sys:String>
<sys:String x:Key="Setup.Ui.Logo.Clear.Success">标题栏图片已清空!</sys:String>
<sys:String x:Key="Setup.Ui.Logo.ClearImage">清空图片</sys:String>
<sys:String x:Key="Setup.Ui.Logo.Image">图片</sys:String>
<sys:String x:Key="Setup.Ui.Logo.LeftAlign">标题栏居左</sys:String>
<sys:String x:Key="Setup.Ui.Logo.None">无</sys:String>
<sys:String x:Key="Setup.Ui.Logo.Text">文本</sys:String>
<sys:String x:Key="Setup.Ui.Logo.TextLabel">标题栏文本</sys:String>
<sys:String x:Key="Setup.Ui.Logo.Title">标题栏</sys:String>
<!-- Setup.Ui.Music -->
<sys:String x:Key="Setup.Ui.Music.AutoStart">打开启动器自动开始播放</sys:String>
<sys:String x:Key="Setup.Ui.Music.Clear">清空背景音乐</sys:String>
<sys:String x:Key="Setup.Ui.Music.Clear.Confirm.Message">即将删除背景音乐文件夹中的所有文件。&#xA;此操作不可撤销,是否确定?</sys:String>
<sys:String x:Key="Setup.Ui.Music.Delete.Success">背景音乐已删除!</sys:String>
<sys:String x:Key="Setup.Ui.Music.Deleting">正在删除背景音乐……</sys:String>
<sys:String x:Key="Setup.Ui.Music.OpenFolder.ToolTip">将背景音乐放到该文件夹,PCL CE 将会播放其中的所有音乐</sys:String>
<sys:String x:Key="Setup.Ui.Music.Refresh">刷新背景音乐</sys:String>
<sys:String x:Key="Setup.Ui.Music.Refresh.ToolTip">刷新播放列表,并播放列表中的音乐</sys:String>
<sys:String x:Key="Setup.Ui.Music.Shuffle">随机播放</sys:String>
<sys:String x:Key="Setup.Ui.Music.Smtc">接入系统媒体传输控制 (SMTC) 控件</sys:String>
<sys:String x:Key="Setup.Ui.Music.Smtc.ToolTip">启用此选项后,可以使用系统控制中心和快捷键等控制 PCL CE 的媒体播放。&#xA;可以与 Lyricify Lite 或 FluentFlyout 等软件搭配使用。</sys:String>
<sys:String x:Key="Setup.Ui.Music.StartInGame">游戏启动后自动开始播放,游戏退出后自动暂停播放</sys:String>
<sys:String x:Key="Setup.Ui.Music.StopInGame">游戏启动后自动暂停播放,游戏退出后自动开始播放</sys:String>
<sys:String x:Key="Setup.Ui.Music.Title">背景音乐</sys:String>
<sys:String x:Key="Setup.Ui.Music.TitleWithCount">背景音乐({0} 首)</sys:String>
<sys:String x:Key="Setup.Ui.Music.Volume">音量</sys:String>
<!-- Setup.Ui.Theme -->
<sys:String x:Key="Setup.Ui.Theme.Color.CatBlue">龙猫蓝</sys:String>
<sys:String x:Key="Setup.Ui.Theme.Color.CrashBlue">死机蓝</sys:String>
<sys:String x:Key="Setup.Ui.Theme.Color.Hmcl">HMCL</sys:String>
<sys:String x:Key="Setup.Ui.Theme.Color.SkyBlue">天空蓝</sys:String>
<sys:String x:Key="Setup.Ui.Theme.Dark">深色</sys:String>
<sys:String x:Key="Setup.Ui.Theme.DarkColor">深色配色</sys:String>
<sys:String x:Key="Setup.Ui.Theme.DarkColor.ToolTip">在深色模式下默认的主题配色</sys:String>
<sys:String x:Key="Setup.Ui.Theme.FollowSystem">跟随系统</sys:String>
<sys:String x:Key="Setup.Ui.Theme.Light">浅色</sys:String>
<sys:String x:Key="Setup.Ui.Theme.LightColor">浅色配色</sys:String>
<sys:String x:Key="Setup.Ui.Theme.Title">主题</sys:String>
<!-- Setup.Update -->
<sys:String x:Key="Setup.Update.Changelog.Empty">开发者似乎忘记提供更新摘要了...也许你可以点击下方看看完整更新日志?</sys:String>
<sys:String x:Key="Setup.Update.Changelog.MoreInfo">有关此更新的更多信息...</sys:String>
<sys:String x:Key="Setup.Update.Changelog.Placeholder">PCL CE 2.14.0 带来了让人眼前一亮的新设计和 Pigeon 智能的多项功能,同时还带来了令人愉快的跨实例工作方式,助你提高工作效率。</sys:String>
<sys:String x:Key="Setup.Update.Changelog.Title">关于此更新</sys:String>
<sys:String x:Key="Setup.Update.Changelog.Unavailable">没有可用的更新日志...</sys:String>
<sys:String x:Key="Setup.Update.Changelog.View">查看更新日志</sys:String>
<sys:String x:Key="Setup.Update.CheckAgain">再次检查</sys:String>
<sys:String x:Key="Setup.Update.CheckFailed">检查更新时出错</sys:String>
<sys:String x:Key="Setup.Update.Checking">正在检查更新...</sys:String>
<sys:String x:Key="Setup.Update.DotNetMissing.DownloadRuntime">下载 .NET 10 运行时</sys:String>
<sys:String x:Key="Setup.Update.DotNetMissing.Message">发现了启动器更新(版本 {0}),但是新版本要求你的电脑安装 .NET 10 才可以运行。&#xA;你需要先安装 .NET 10 才可以继续更新。&#xA;&#xA;点击下方按钮打开网页,然后选择 ⌈.NET 桌面运行时⌋ 中的 {1} 选项下载。</sys:String>
<sys:String x:Key="Setup.Update.DotNetMissing.Title">启动器更新 - 缺少运行环境</sys:String>
<sys:String x:Key="Setup.Update.Error.NetworkFailed">无法获取最新版本信息,请检查网络连接</sys:String>
<sys:String x:Key="Setup.Update.Install">下载并安装</sys:String>
<sys:String x:Key="Setup.Update.Latest">已是最新版本</sys:String>
<sys:String x:Key="Setup.Update.MirrorChyanCdk.Get">获取 CDK</sys:String>
<sys:String x:Key="Setup.Update.MirrorChyanCdk.Label">Mirror 酱 CDK</sys:String>
<sys:String x:Key="Setup.Update.MirrorChyanCdk.ToolTip">Mirror 酱 (mirrorchyan.com) 是一个付费的第三方应用分发平台,用于提供国内有偿高速下载源。&#xA;这是一项可选服务,不填入 CDK 不影响正常使用与更新。&#xA;如果你有 Mirror 酱的 CDK,可以提供给 PCL CE,启动器会优先使用他们的高速下载源下载软件更新,也能缓解社区公益更新服务器的一些压力……</sys:String>
<sys:String x:Key="Setup.Update.OtherOptions.Placeholder">AquaCL 3 是 PCL CE 的第一个主要更新,带来了让人眼前一亮的新设计,使用了最新开发技术,完全重构了基础体验,并更支持 macOS、Linux 等其他平台。</sys:String>
<sys:String x:Key="Setup.Update.OtherOptions.Title">其他更新选项 - 升级到 AquaCL</sys:String>
<sys:String x:Key="Setup.Update.RestartInstall">重启安装</sys:String>
<!-- Setup.Update.Auto -->
<sys:String x:Key="Setup.Update.Auto.Disabled">不自动检查更新(不推荐)</sys:String>
<sys:String x:Key="Setup.Update.Auto.Disabled.ToolTip">关闭自动更新检查。这可能导致你无法及时获得更新。</sys:String>
<sys:String x:Key="Setup.Update.Auto.DownloadAndInstall">自动下载并安装更新</sys:String>
<sys:String x:Key="Setup.Update.Auto.DownloadAndInstall.ToolTip">启动器将会自动检查并下载可用更新,并在关闭启动器后自动安装更新。</sys:String>
<sys:String x:Key="Setup.Update.Auto.DownloadAndNotify">自动下载并提示更新</sys:String>
<sys:String x:Key="Setup.Update.Auto.DownloadAndNotify.ToolTip">启动器将会自动检查并下载可用更新,并在完成后提示你安装更新。</sys:String>
<sys:String x:Key="Setup.Update.Auto.NotifyOnly">提示更新</sys:String>
<sys:String x:Key="Setup.Update.Auto.NotifyOnly.ToolTip">启动器将会自动检查可用更新,然后提示你下载并安装更新。</sys:String>
<sys:String x:Key="Setup.Update.Auto.Title">自动更新设置</sys:String>
<!-- Setup.Update.Channel -->
<sys:String x:Key="Setup.Update.Channel.Beta">测试版 / Beta</sys:String>
<sys:String x:Key="Setup.Update.Channel.Beta.ToolTip">可能不稳定的测试版本,将会进行功能的早期测试,适合希望帮助测试且有一定技术能力的用户使用。</sys:String>
<sys:String x:Key="Setup.Update.Channel.Beta.Warning.Message">你正在切换启动器更新通道到测试版。&#xA;测试版可以提供下个版本更新内容的预览,但可能会包含未经充分测试的功能,稳定性欠佳。&#xA;&#xA;在升级到测试版后,你需要等待下一个正式版发布,或是手动重新下载启动器来切换到正式版。&#xA;该选项仅推荐具有一定基础知识和能力的用户选择。如果你正在制作整合包,请使用正式版!</sys:String>
<sys:String x:Key="Setup.Update.Channel.Common.Warning.Confirm">我已知晓</sys:String>
<sys:String x:Key="Setup.Update.Channel.Common.Warning.Title">继续之前...</sys:String>
<sys:String x:Key="Setup.Update.Channel.Release">正式版 / Release</sys:String>
<sys:String x:Key="Setup.Update.Channel.Release.ToolTip">较为稳定的版本,仅推送修复性更新与部分内置资源更新,适合大多数用户使用。</sys:String>
<sys:String x:Key="Setup.Update.Channel.Title">更新通道</sys:String>
<!-- Setup.Update.Channel.Dev -->
<sys:String x:Key="Setup.Update.Channel.Dev">开发版 / Dev</sys:String>
<sys:String x:Key="Setup.Update.Channel.Dev.FinalConfirm.ExpectedInput">我确认切换到此分支并已知晓风险</sys:String>
<sys:String x:Key="Setup.Update.Channel.Dev.FinalConfirm.Message">你确定要切换到开发版通道吗?&#xA;开发版可能存在严重问题,甚至无法启动!&#xA;&#xA;在升级到开发版后,将无法切换回其他任何更新通道,只能手动重新下载启动器来切换回正式版或测试版。&#xA;&#xA;该选项仅推荐高级用户选择。如果你正在制作整合包,请使用正式版!&#xA;请输入 '{0}' 以确认。</sys:String>
<sys:String x:Key="Setup.Update.Channel.Dev.FinalConfirm.Submit">提交</sys:String>
<sys:String x:Key="Setup.Update.Channel.Dev.FinalConfirm.Title">最终确认</sys:String>
<sys:String x:Key="Setup.Update.Channel.Dev.FinalConfirm.WrongInput">你输入了错误的内容...</sys:String>
<sys:String x:Key="Setup.Update.Channel.Dev.ToolTip">可能极不稳定的开发版本,直接从最新源代码构建,适合高度技术性用户。&#xA;可能需要具备较好的互联网连接质量才能顺利获取此通道更新。</sys:String>
<sys:String x:Key="Setup.Update.Channel.Dev.Warning.Message">你正在切换启动器更新通道到开发版。&#xA;该通道可第一时间获取基于最新代码构建的开发版本,但可能极不稳定,甚至直接无法启动。&#xA;&#xA;在升级到开发版后,只能手动重新下载启动器来切换回正式版或测试版。&#xA;该选项仅推荐高级用户选择。如果你正在制作整合包,请使用正式版!</sys:String>
<!-- Speed -->
<sys:String x:Key="Speed.Error.ClickToCopy">单击复制错误详情</sys:String>
<sys:String x:Key="Speed.Error.Copied">已复制错误详情!</sys:String>
<sys:String x:Key="Speed.Error.OperationFailed">任务管理操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Speed.Progress.RemainingFiles">剩余文件</sys:String>
<sys:String x:Key="Speed.Progress.RemainingThreads">剩余线程</sys:String>
<sys:String x:Key="Speed.Progress.Speed">下载速度</sys:String>
<sys:String x:Key="Speed.Progress.Total">总进度</sys:String>
<!-- SystemDialog -->
<sys:String x:Key="SystemDialog.Browser.OpenFailed.Message">PCL CE 无法打开此网页。网址已复制到剪贴板,可手动粘贴访问。&#xA;&#xA;网址:{0}</sys:String>
<sys:String x:Key="SystemDialog.Browser.OpenFailed.Title">无法打开网页</sys:String>
<sys:String x:Key="SystemDialog.Fatal.Message.WithFeedbackGuidance">{0}&#xA;&#xA;如果你认为这是启动器的问题,请提交反馈,以便开发者调查。&#xA;导出日志:设置 → 查看日志 → 导出全部日志</sys:String>
<sys:String x:Key="SystemDialog.Fatal.Title">致命错误</sys:String>
<sys:String x:Key="SystemDialog.File.AllFilesFilter">所有文件|*.*</sys:String>
<sys:String x:Key="SystemDialog.File.OpenFailed.Message">无法打开文件或程序。&#xA;&#xA;路径:{0}</sys:String>
<sys:String x:Key="SystemDialog.File.SelectTitle">选择文件</sys:String>
<sys:String x:Key="SystemDialog.Folder.OpenFailed.Message">无法打开文件资源管理器。请尝试关闭安全软件(例如 360 安全卫士)。&#xA;&#xA;路径:{0}</sys:String>
<sys:String x:Key="SystemDialog.Folder.SelectTitle">选择文件夹</sys:String>
<sys:String x:Key="SystemDialog.Startup.DotNetRuntimeOutdated.Message">你的 .NET 桌面运行时已过期或损坏。请重新安装 .NET 10。</sys:String>
<sys:String x:Key="SystemDialog.Startup.InitializationTitle">PCL CE 初始化失败</sys:String>
<!-- SystemDialog.Error -->
<sys:String x:Key="SystemDialog.Error.Detail.WithException">{0}&#xA;&#xA;详细信息:&#xA;{1}</sys:String>
<sys:String x:Key="SystemDialog.Error.Message.WithLogExportGuidance">{0}&#xA;&#xA;若要寻求帮助,请不要关闭启动器。请立即导出日志(设置 → 查看日志 → 导出日志),并发送导出的日志压缩包;仅发送此窗口的截图通常无法帮助定位问题。</sys:String>
<sys:String x:Key="SystemDialog.Error.OperationFailed.Message">执行操作时发生错误。&#xA;&#xA;操作信息:&#xA;{0}&#xA;&#xA;详细信息:&#xA;{1}</sys:String>
<sys:String x:Key="SystemDialog.Error.OperationFailed.RuntimeMessage">执行操作时发生错误。&#xA;&#xA;操作信息:&#xA;{0}&#xA;&#xA;详细信息:&#xA;{1}&#xA;&#xA;与系统底层交互失败。请尝试重新安装 .NET 10。</sys:String>
<sys:String x:Key="SystemDialog.Error.Title">错误</sys:String>
<sys:String x:Key="SystemDialog.Error.Unexpected.Message">发生意外错误。</sys:String>
<sys:String x:Key="SystemDialog.Error.Unexpected.Title">发生意外错误</sys:String>
<sys:String x:Key="SystemDialog.Error.UserVisible.Message">发生错误。&#xA;&#xA;详细信息:&#xA;{0}</sys:String>
<!-- Tools -->
<sys:String x:Key="Tools.Error.OperationFailed">工具页操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Tools.Left.Lobby">大厅</sys:String>
<sys:String x:Key="Tools.Left.Multiplayer">联机</sys:String>
<sys:String x:Key="Tools.Left.Utilities">奇妙小工具</sys:String>
<sys:String x:Key="Tools.Left.Utilities.Title">百宝箱</sys:String>
<!-- Tools.GameLink -->
<sys:String x:Key="Tools.GameLink.Announcement.Format">[{0}] {1}</sys:String>
<sys:String x:Key="Tools.GameLink.Announcement.Important">重要</sys:String>
<sys:String x:Key="Tools.GameLink.Announcement.Notice">提示</sys:String>
<sys:String x:Key="Tools.GameLink.Announcement.Warning">注意</sys:String>
<sys:String x:Key="Tools.GameLink.CopyIp.Back">返回</sys:String>
<sys:String x:Key="Tools.GameLink.CopyIp.Message">大厅创建者的游戏地址:{0}&#xA;注意:仅推荐在 MC 多人游戏列表不显示大厅广播时使用 IP 连接!通过 IP 连接将可能要求使用正版档案。</sys:String>
<sys:String x:Key="Tools.GameLink.CopyIp.Title">复制 IP</sys:String>
<sys:String x:Key="Tools.GameLink.Create.Action">创建</sys:String>
<sys:String x:Key="Tools.GameLink.Create.Description">1、进入世界后,在游戏菜单中选择【对局域网开放】&#xA;2、在下方选择此游戏实例,单击【创建】&#xA;3、成功创建大厅后,复制大厅编号并发送给你的朋友</sys:String>
<sys:String x:Key="Tools.GameLink.Create.EnterPort">请输入端口</sys:String>
<sys:String x:Key="Tools.GameLink.Create.ManualInput">手动输入</sys:String>
<sys:String x:Key="Tools.GameLink.Create.NoWorld">请先选择一个要联机的世界!</sys:String>
<sys:String x:Key="Tools.GameLink.Create.NotMcPort">这似乎不是个 MC 服务端口...</sys:String>
<sys:String x:Key="Tools.GameLink.Create.Title">创建大厅</sys:String>
<sys:String x:Key="Tools.GameLink.EasyTierVersion">联机双方目前使用的启动器版本不同,这可能会导致一些连接问题&#xA;强烈建议双方升级到最新版本后再继续联机</sys:String>
<sys:String x:Key="Tools.GameLink.Error.ConnectFailed">无法连接到大厅服务器</sys:String>
<sys:String x:Key="Tools.GameLink.Error.NetworkTestFailed">获取网络测试结果失败。请查看错误详情。</sys:String>
<sys:String x:Key="Tools.GameLink.Error.ServerExit">在服务器退出时发生了错误!</sys:String>
<sys:String x:Key="Tools.GameLink.Error.ServerMessage">与大厅服务器的连接出现错误。&#xA;&#xA;原始错误信息:&#xA;{0}</sys:String>
<sys:String x:Key="Tools.GameLink.Error.UpdateRequired">请更新到最新版 PCL CE 以使用大厅</sys:String>
<sys:String x:Key="Tools.GameLink.Eula.Disabled">联机功能已停用!</sys:String>
<sys:String x:Key="Tools.GameLink.Eula.RevokeConfirm">你确定要撤销联机协议授权吗?</sys:String>
<sys:String x:Key="Tools.GameLink.Eula.RevokeTitle">撤销授权确认</sys:String>
<sys:String x:Key="Tools.GameLink.Exit.ConfirmMessage">你确定要退出大厅吗?</sys:String>
<sys:String x:Key="Tools.GameLink.Exit.ConfirmMessageWithHost">你确定要退出大厅吗?&#xA;由于你是大厅创建者,退出后此大厅将会自动解散。</sys:String>
<sys:String x:Key="Tools.GameLink.Exit.ConfirmTitle">确认退出</sys:String>
<sys:String x:Key="Tools.GameLink.Exit.Disbanded">由于你关闭了联机中的 MC 实例,大厅已自动解散。</sys:String>
<sys:String x:Key="Tools.GameLink.Exit.DisbandedTitle">大厅已解散</sys:String>
<sys:String x:Key="Tools.GameLink.Exit.HostWarning">由于你是大厅创建者,退出后此大厅将会自动解散。</sys:String>
<sys:String x:Key="Tools.GameLink.Join.Action">加入</sys:String>
<sys:String x:Key="Tools.GameLink.Join.Clear">清除</sys:String>
<sys:String x:Key="Tools.GameLink.Join.Description">1、在下方输入朋友发送给你的大厅编号,单击【加入】&#xA;2、启动游戏,选择【多人游戏】,在局域网游戏中加入大厅</sys:String>
<sys:String x:Key="Tools.GameLink.Join.IdHint">编号应该长得像这样:U/NNNN-AAAA-SSSS-EEEE</sys:String>
<sys:String x:Key="Tools.GameLink.Join.InvalidText">大厅编号不正确,请检查后重新输入</sys:String>
<sys:String x:Key="Tools.GameLink.Join.Paste">粘贴</sys:String>
<sys:String x:Key="Tools.GameLink.Join.Title">加入大厅</sys:String>
<sys:String x:Key="Tools.GameLink.Link.Disable">停用联机功能</sys:String>
<sys:String x:Key="Tools.GameLink.Link.EasyTier">EasyTier 工具官网</sys:String>
<sys:String x:Key="Tools.GameLink.Link.Friends">友情链接:</sys:String>
<sys:String x:Key="Tools.GameLink.Link.Privacy">大厅隐私协议</sys:String>
<sys:String x:Key="Tools.GameLink.Link.Report">违法违规举报</sys:String>
<sys:String x:Key="Tools.GameLink.Link.Terms">Natayark Network 用户协议与隐私政策</sys:String>
<sys:String x:Key="Tools.GameLink.Loading.ConnectingServer">正在连接到大厅服务器...</sys:String>
<sys:String x:Key="Tools.GameLink.Loading.Creating">正在创建大厅</sys:String>
<sys:String x:Key="Tools.GameLink.Loading.DownloadingModule">下载联机模块中……</sys:String>
<sys:String x:Key="Tools.GameLink.Member.ListCount">大厅成员列表(共 {0} 人)</sys:String>
<sys:String x:Key="Tools.GameLink.Member.ListLoading">大厅成员列表(正在获取信息)</sys:String>
<sys:String x:Key="Tools.GameLink.Nat.Failed">测试失败</sys:String>
<sys:String x:Key="Tools.GameLink.Nat.Result">UDP: {0}, TCP: {1}</sys:String>
<sys:String x:Key="Tools.GameLink.Nat.Test">点击测试</sys:String>
<sys:String x:Key="Tools.GameLink.Nat.Testing">正在测试</sys:String>
<sys:String x:Key="Tools.GameLink.Nat.Title">NAT 类型</sys:String>
<sys:String x:Key="Tools.GameLink.Player.Details">{0} {1}</sys:String>
<sys:String x:Key="Tools.GameLink.Player.Host">[主机]</sys:String>
<sys:String x:Key="Tools.GameLink.Player.InfoMessage">用户名:{0}&#xA;联机协议客户端标识:{1}&#xA;&#xA;此处数据仅供参考,请以实际游玩体验为准。&#xA;&#xA;若想了解 NAT 类型与其如何影响联机体验,请前往界面左侧的常见问题一栏。</sys:String>
<sys:String x:Key="Tools.GameLink.Player.InfoTitle">玩家 {0} 的详细信息</sys:String>
<!-- Tools.GameLink.Agreement -->
<sys:String x:Key="Tools.GameLink.Agreement.Accept">我已阅读并同意</sys:String>
<sys:String x:Key="Tools.GameLink.Agreement.Body">使用大厅功能即代表你同意下列条款:&#xA;&#xA;我承诺严格遵守中国大陆相关法律法规,不会将大厅功能用于违法违规用途。&#xA;我承诺使用大厅功能带来的一切风险自行承担。&#xA;我已知晓并同意 PCL CE 收集经处理的本机识别码、Natayark ID 与其他信息并在必要时提供给执法部门。&#xA;为保护未成年人个人信息,使用联机大厅前,我确认我已满十四周岁。&#xA;&#xA;另外,你还需要同意 PCL CE 大厅相关隐私政策及《Natayark OpenID 服务条款》。</sys:String>
<sys:String x:Key="Tools.GameLink.Agreement.Description">此处列出了 PCL CE 大厅使用的相关服务文档及介绍</sys:String>
<sys:String x:Key="Tools.GameLink.Agreement.NatayarkTerms">Natayark OpenID 服务条款</sys:String>
<sys:String x:Key="Tools.GameLink.Agreement.NatayarkTermsInfo">查看 Natayark OpenID 服务条款</sys:String>
<sys:String x:Key="Tools.GameLink.Agreement.PrivacyPolicy">PCL CE 大厅相关隐私政策</sys:String>
<sys:String x:Key="Tools.GameLink.Agreement.PrivacyPolicyInfo">了解 PCL CE 如何处理您的个人信息</sys:String>
<sys:String x:Key="Tools.GameLink.Agreement.Title">PCL CE 联机大厅说明与条款</sys:String>
<!-- Tools.GameLink.Finish -->
<sys:String x:Key="Tools.GameLink.Finish.Actions">大厅操作</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.Checking">检测中</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.CloseLobby">关闭大厅</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.Connected">已连接</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.Connecting">连接中</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.ConnectionStatus">连接状况</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.ConnectionType">连接方式</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.CopyLobbyId">复制大厅编号</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.CopyVirtualIp">复制虚拟 IP</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.Exit">退出大厅</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.Guest">加入者</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.Host">创建者</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.InfoTitle">大厅信息</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.LobbyId">大厅编号</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.PingMs">{0}ms</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.PingToHost">与大厅创建者的延迟</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.Unavailable">暂不可用</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.UserName">用户名</sys:String>
<sys:String x:Key="Tools.GameLink.Finish.UserType">用户类型</sys:String>
<!-- Tools.GameLink.Natayark -->
<sys:String x:Key="Tools.GameLink.Natayark.Abnormal">(异常)</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.Account">Natayark 账户</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.AccountExpired">Natayark 账号信息已过期,请前往设置重新登录!</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.BrowserContinue">请在浏览器中继续...</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.Continue">继续</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.FetchFailed">获取信息失败</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.InvalidAuthCode">Naid 验证代码无效</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.Loading">正在加载...</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.Login">点击登录 Natayark 账户</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.LoginComplete">已完成登录操作</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.LoginFailed">Naid 登录失败,请尝试前往设置重新登录</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.LoginPrompt">PCL CE 将会打开一个登录页面,请在浏览器中完成登录操作,然后回到启动器继续操作。</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.LoginTitle">登录至 Natayark Network</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.LogoutComplete">已退出登录!</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.LogoutConfirm">你确定要退出登录吗?</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.LogoutTitle">退出登录</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.ProfileLoadFailed">获取 Natayark 用户数据失败,请尝试前往设置重新登录</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.TokenExpired">Natayark ID 令牌已过期,请重新登录</sys:String>
<sys:String x:Key="Tools.GameLink.Natayark.TokenInvalid">Naid Access Token 无效,尝试刷新登录</sys:String>
<!-- Tools.ServerQuery -->
<sys:String x:Key="Tools.ServerQuery.Action.Query">查询</sys:String>
<sys:String x:Key="Tools.ServerQuery.Address.Hint">输入服务器地址</sys:String>
<sys:String x:Key="Tools.ServerQuery.State.NoInfo">未返回服务器信息</sys:String>
<sys:String x:Key="Tools.ServerQuery.State.Querying">查询中...</sys:String>
<sys:String x:Key="Tools.ServerQuery.Title.MinecraftServer">Minecraft 服务器</sys:String>
<!-- Tools.ServerQuery.Error -->
<sys:String x:Key="Tools.ServerQuery.Error.AddressTooLong">服务器地址过长</sys:String>
<sys:String x:Key="Tools.ServerQuery.Error.EmptyPacket">服务器返回了空数据包</sys:String>
<sys:String x:Key="Tools.ServerQuery.Error.IncompleteConnection">服务器在返回完整状态数据包前提前断开连接。</sys:String>
<sys:String x:Key="Tools.ServerQuery.Error.InvalidResponse">服务器返回了错误的信息</sys:String>
<sys:String x:Key="Tools.ServerQuery.Error.PongDataLength">Pong 数据长度必须为 8 字节</sys:String>
<sys:String x:Key="Tools.ServerQuery.Error.StaleConnection">服务器在返回状态后提前断开连接,未返回 pong 数据包,无法计算延迟。</sys:String>
<sys:String x:Key="Tools.ServerQuery.Error.Timeout.Connect">连接超时</sys:String>
<sys:String x:Key="Tools.ServerQuery.Error.Timeout.ReadWrite">数据读写超时</sys:String>
<sys:String x:Key="Tools.ServerQuery.Error.UnableToConnect">无法连接: {0}</sys:String>
<!-- Tools.Test -->
<sys:String x:Key="Tools.Test.CeNotice">为便于维护,PCL CE 中不包含百宝箱功能……</sys:String>
<sys:String x:Key="Tools.Test.Crash.ManualCrash">手动崩溃</sys:String>
<sys:String x:Key="Tools.Test.Crash.Title">崩溃测试</sys:String>
<sys:String x:Key="Tools.Test.Dependency.Checking">开始环境检查……</sys:String>
<sys:String x:Key="Tools.Test.Dependency.Later">稍后</sys:String>
<sys:String x:Key="Tools.Test.Dependency.MissingMessage">当前系统环境缺失软件运行所需依赖"{0}"&#xA;&#xA;点击确定打开微软应用商店安装</sys:String>
<sys:String x:Key="Tools.Test.Dependency.Title">依赖检查</sys:String>
<sys:String x:Key="Tools.Test.Error.OperationFailed">测试工具操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Tools.Test.LaunchCount.Message">PCL CE 已经为你启动了 {0} 次游戏了。</sys:String>
<sys:String x:Key="Tools.Test.LaunchCount.Title">查看启动计数</sys:String>
<sys:String x:Key="Tools.Test.ServerQuery.Title">瞅眼服务器</sys:String>
<sys:String x:Key="Tools.Test.Title">百宝箱</sys:String>
<!-- Tools.Test.Achievement -->
<sys:String x:Key="Tools.Test.Achievement.FetchFailed">获取成就图片失败,请检查文字是否包含特殊字符</sys:String>
<sys:String x:Key="Tools.Test.Achievement.FileFilter">PNG 图片 (*.png)|*.png</sys:String>
<sys:String x:Key="Tools.Test.Achievement.InvalidId">请输入正确的物品 ID</sys:String>
<sys:String x:Key="Tools.Test.Achievement.ItemId">物品名(ID</sys:String>
<sys:String x:Key="Tools.Test.Achievement.Line1">第一行</sys:String>
<sys:String x:Key="Tools.Test.Achievement.Line2">第二行(可选)</sys:String>
<sys:String x:Key="Tools.Test.Achievement.Name">成就名</sys:String>
<sys:String x:Key="Tools.Test.Achievement.Preview">预览成就图像</sys:String>
<sys:String x:Key="Tools.Test.Achievement.Save">保存图片</sys:String>
<sys:String x:Key="Tools.Test.Achievement.Saved">自定义成就图片已保存!</sys:String>
<sys:String x:Key="Tools.Test.Achievement.Title">自定义成就图片生成器 (仅支持英文)</sys:String>
<!-- Tools.Test.Avatar -->
<sys:String x:Key="Tools.Test.Avatar.FileFilter">图像文件(*.png)|*.png</sys:String>
<sys:String x:Key="Tools.Test.Avatar.GenerateFailed">生成头像失败:{0}</sys:String>
<sys:String x:Key="Tools.Test.Avatar.Generated">头像生成成功!</sys:String>
<sys:String x:Key="Tools.Test.Avatar.InvalidSize">图片的大小不正确!请确认你选择了正确的文件!</sys:String>
<sys:String x:Key="Tools.Test.Avatar.Save">保存头像</sys:String>
<sys:String x:Key="Tools.Test.Avatar.Saved">头像保存成功!</sys:String>
<sys:String x:Key="Tools.Test.Avatar.SelectFirst">请先选择皮肤!</sys:String>
<sys:String x:Key="Tools.Test.Avatar.SelectSkin">选择皮肤</sys:String>
<sys:String x:Key="Tools.Test.Avatar.SelectSkinFile">选择皮肤文件</sys:String>
<sys:String x:Key="Tools.Test.Avatar.Size">头像大小:</sys:String>
<sys:String x:Key="Tools.Test.Avatar.Title">皮肤头像生成器</sys:String>
<!-- Tools.Test.Clean -->
<sys:String x:Key="Tools.Test.Clean.Cleared">缓存已清理</sys:String>
<sys:String x:Key="Tools.Test.Clean.ClearedMessage">清理了 {0} 个文件!&#xA;PCL CE 即将自动重启……</sys:String>
<sys:String x:Key="Tools.Test.Clean.CloseGameFirst">请先关闭所有运行中的游戏……</sys:String>
<sys:String x:Key="Tools.Test.Clean.ConfirmMessage">即将清理游戏日志、错误报告、缓存等文件。&#xA;虽然应该没人往这些地方放重要文件,但还是问一下,是否确认继续?&#xA;&#xA;在完成清理后,PCL CE 将自动重启。</sys:String>
<sys:String x:Key="Tools.Test.Clean.ConfirmTitle">清理确认</sys:String>
<sys:String x:Key="Tools.Test.Clean.NoFiles">没有找到任何可以清理的文件!</sys:String>
<sys:String x:Key="Tools.Test.Clean.Title">清理游戏垃圾</sys:String>
<sys:String x:Key="Tools.Test.Clean.ToolTip">清理 PCL CE 的缓存与 MC 的日志、崩溃报告等垃圾文件</sys:String>
<sys:String x:Key="Tools.Test.Clean.WaitForDownload">请在所有下载任务完成后再来清理吧……</sys:String>
<!-- Tools.Test.CustomDownload -->
<sys:String x:Key="Tools.Test.CustomDownload.Aborted">{0}已取消!</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.Description">使用 PCL CE 的高速多线程下载引擎下载任意文件。请注意,部分网站(例如百度网盘)可能会报错 (403) 已禁止,无法正常下载。</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.FileName">文件名</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.Finished">{0}完成!</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.LoaderName">自定义下载文件:{0} </sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.LoaderTitle">自定义下载 ({0}) </sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.SaveTo">保存到</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.Select">选择</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.SelectLocation">选择文件保存位置</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.Start">开始下载</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.Title">下载自定义文件</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.Url">下载地址</sys:String>
<sys:String x:Key="Tools.Test.CustomDownload.UserAgent">User-Agent</sys:String>
<!-- Tools.Test.Luck -->
<sys:String x:Key="Tools.Test.Luck.MessageBad">你今天的人品值是:{0}... {1}</sys:String>
<sys:String x:Key="Tools.Test.Luck.MessageGood">你今天的人品值是:{0}{1}</sys:String>
<sys:String x:Key="Tools.Test.Luck.MsgboxTitle">今日人品 - {0}</sys:String>
<sys:String x:Key="Tools.Test.Luck.Rating0">(是百分制哦)</sys:String>
<sys:String x:Key="Tools.Test.Luck.Rating10">不会吧!</sys:String>
<sys:String x:Key="Tools.Test.Luck.Rating100">100100&#xA;隐藏主题 欧皇…… 不对,社区版应该没有这玩意……</sys:String>
<sys:String x:Key="Tools.Test.Luck.Rating30">呜...</sys:String>
<sys:String x:Key="Tools.Test.Luck.Rating40">勉强还行吧...</sys:String>
<sys:String x:Key="Tools.Test.Luck.Rating60">还行啦,还行啦</sys:String>
<sys:String x:Key="Tools.Test.Luck.Rating90">好评如潮!</sys:String>
<sys:String x:Key="Tools.Test.Luck.Rating95">差一点就到100了呢...</sys:String>
<sys:String x:Key="Tools.Test.Luck.Title">今日人品</sys:String>
<!-- Tools.Test.Shortcut -->
<sys:String x:Key="Tools.Test.Shortcut.ConfirmMessage">这个快捷方式不会自动移除,在删除/移动启动器前请手动移除快捷方式。&#xA;&#xA;{0}位置: {1}&#xA;{2}位置: {3}</sys:String>
<sys:String x:Key="Tools.Test.Shortcut.Created">已在{0}创建快捷方式</sys:String>
<sys:String x:Key="Tools.Test.Shortcut.Desktop">桌面</sys:String>
<sys:String x:Key="Tools.Test.Shortcut.FileName">PCL CE{0}</sys:String>
<sys:String x:Key="Tools.Test.Shortcut.SelectLocation">选择快捷方式位置</sys:String>
<sys:String x:Key="Tools.Test.Shortcut.StartMenu">开始菜单</sys:String>
<sys:String x:Key="Tools.Test.Shortcut.Title">创建快捷方式</sys:String>
<sys:String x:Key="Tools.Test.Shortcut.ToolTip">创建一个指向 PCL CE 可执行文件的快捷方式</sys:String>
<!-- Tools.Test.Skin -->
<sys:String x:Key="Tools.Test.Skin.Fetching">正在获取皮肤...</sys:String>
<sys:String x:Key="Tools.Test.Skin.FileFilter">皮肤图片文件(*.png)|*.png</sys:String>
<sys:String x:Key="Tools.Test.Skin.InvalidId">这不是一个有效的 ID...</sys:String>
<sys:String x:Key="Tools.Test.Skin.PlayerName">正版玩家名</sys:String>
<sys:String x:Key="Tools.Test.Skin.Save">保存皮肤</sys:String>
<sys:String x:Key="Tools.Test.Skin.Saved">玩家 {0} 的皮肤已保存!</sys:String>
<sys:String x:Key="Tools.Test.Skin.Title">下载正版玩家的皮肤</sys:String>
<sys:String x:Key="Tools.Test.Skin.TooFrequent">获取皮肤太过频繁,请 5 分钟之后再试!</sys:String>
<!-- Update -->
<sys:String x:Key="Update.Action">更新</sys:String>
<sys:String x:Key="Update.Available">启动器有新版本可用({0} -&gt; {1}&#xA;是否立即更新?</sys:String>
<sys:String x:Key="Update.Check.Failed">无法获取最新版本信息,请检查网络连接</sys:String>
<sys:String x:Key="Update.CommunityNotice.Body">你正在使用来自 PCL-Community 的 PCL CE,遇到问题请不要向原版仓库反馈!&#xA;PCL-Community 及其成员与龙腾猫跃无从属关系,且均不会为您的使用做担保。&#xA;&#xA;如果你是意外下载的 PCL CE,建议下载原版 PCL 使用。&#xA;如果你是意外下载的 PCL CE,建议下载原版 PCL 使用。&#xA;如果你是意外下载的 PCL CE,建议下载原版 PCL 使用。&#xA;&#xA;该版本与原版的特性区别:&#xA;- 主题切换:仅部分固定蓝色系主题,没有计划新增其它主题。&#xA;- 百宝箱:缺失部分原版中的内容(回声洞、千万别点)。&#xA;&#xA;此提示会在启动器更新后展示一次。</sys:String>
<sys:String x:Key="Update.CommunityNotice.Confirm">我知道了</sys:String>
<sys:String x:Key="Update.CommunityNotice.Title">PCL CE 版本说明</sys:String>
<sys:String x:Key="Update.Error.FetchFailed">获取启动器更新失败,请检查网络连接</sys:String>
<sys:String x:Key="Update.Error.FileNotFound">找不到更新文件</sys:String>
<sys:String x:Key="Update.Error.OperationFailed">更新操作失败。请查看错误详情。</sys:String>
<sys:String x:Key="Update.Error.Sha256Mismatch">更新文件 SHA256 不正确,应该为 {0},实际为 {1}</sys:String>
<sys:String x:Key="Update.Error.UnableToGetUpdate">无法获取更新</sys:String>
<sys:String x:Key="Update.Error.UpdateBlocked">更新失败</sys:String>
<sys:String x:Key="Update.Error.UpdateBlockedMessage">由于被 Windows 安全中心拦截,或者存在权限问题,导致 PCL CE 无法更新。&#xA;请将 PCL CE 所在文件夹加入白名单,或者手动用 {0}PCL\Plain Craft Launcher Community Edition.exe 替换当前文件!</sys:String>
<sys:String x:Key="Update.Service.PclCe">PCL CE 服务</sys:String>
<sys:String x:Key="Update.Title">启动器更新</sys:String>
<!-- Update.Task -->
<sys:String x:Key="Update.Task.ApplyFile">应用文件</sys:String>
<sys:String x:Key="Update.Task.Check">校验更新</sys:String>
<sys:String x:Key="Update.Task.DownloadFile">下载文件</sys:String>
<sys:String x:Key="Update.Task.DownloadLatestStable">下载最新稳定版</sys:String>
<sys:String x:Key="Update.Task.DownloadUpdateFile">下载更新文件</sys:String>
<sys:String x:Key="Update.Task.GetAnnouncementFailed">获取公告信息失败</sys:String>
<sys:String x:Key="Update.Task.GetDownloadInfo">获取下载信息</sys:String>
<sys:String x:Key="Update.Task.GetVersionInfo">获取版本信息</sys:String>
<sys:String x:Key="Update.Task.GetVersionInfoFailed">获取版本信息失败</sys:String>
<sys:String x:Key="Update.Task.Install">安装更新</sys:String>
<sys:String x:Key="Update.Task.Prepare">准备更新</sys:String>
<sys:String x:Key="Update.Task.RefreshSettings">刷新设置 UI</sys:String>
<sys:String x:Key="Update.Task.RestartInstall">重启安装</sys:String>
<sys:String x:Key="Update.Task.ShowButton">显示按钮</sys:String>
<!-- Watcher -->
<sys:String x:Key="Watcher.Crash.AbnormalExit">Minecraft 返回值异常,可能已崩溃</sys:String>
<sys:String x:Key="Watcher.Crash.AnalysisStart">崩溃分析开始</sys:String>
<sys:String x:Key="Watcher.Crash.Detected">Minecraft 已崩溃,将在 2 秒后开始崩溃分析</sys:String>
<sys:String x:Key="Watcher.Crash.DetectedDisabled">Minecraft 已崩溃。由于崩溃分析已被禁用,将不会开始分析</sys:String>
<sys:String x:Key="Watcher.Crash.Hint">检测到 Minecraft 出现错误,错误分析已开始……</sys:String>
<sys:String x:Key="Watcher.Crash.Suspected">Minecraft 尚未加载完成,可能已崩溃</sys:String>
<sys:String x:Key="Watcher.Exited">Minecraft 日志监控已退出</sys:String>
<sys:String x:Key="Watcher.FmlWindowLoaded">FML 窗口已加载:{0}{1}</sys:String>
<sys:String x:Key="Watcher.Kill.Attempt">尝试强制结束 Minecraft 进程</sys:String>
<sys:String x:Key="Watcher.Kill.Failed">强制结束 Minecraft 进程失败</sys:String>
<sys:String x:Key="Watcher.Kill.Success">已强制结束 Minecraft 进程</sys:String>
<sys:String x:Key="Watcher.Kill.TaskkillAttempt">进程仍未退出,尝试使用 taskkill.exe</sys:String>
<sys:String x:Key="Watcher.Kill.TaskkillResult">执行 taskkill.exe 结果:{0}</sys:String>
<sys:String x:Key="Watcher.Kill.Timeout">强制结束 Minecraft 进程失败:等待进程退出超时</sys:String>
<sys:String x:Key="Watcher.LoadComplete">Minecraft 加载已完成</sys:String>
<sys:String x:Key="Watcher.Log.CloseDetected">识别为关闭的 Log{0}</sys:String>
<sys:String x:Key="Watcher.Log.CrashDetected">识别为崩溃的 Log{0}</sys:String>
<sys:String x:Key="Watcher.ProcessExited">Minecraft 已退出,返回值:{0}</sys:String>
<sys:String x:Key="Watcher.Progress.LogAppeared">日志 1/5:已出现日志输出</sys:String>
<sys:String x:Key="Watcher.Progress.LwjglConfirmed">日志 3/5LWJGL 版本已确认</sys:String>
<sys:String x:Key="Watcher.Progress.OpenAlLoaded">日志 4/5OpenAL 已加载</sys:String>
<sys:String x:Key="Watcher.Progress.TexturesLoaded">日志 5/5:材质已加载</sys:String>
<sys:String x:Key="Watcher.Progress.UserSet">日志 2/5:游戏用户已设置</sys:String>
<sys:String x:Key="Watcher.SecurityBlocked">由于反作弊或安全软件拦截,PCL CE 无法操作游戏窗口</sys:String>
<sys:String x:Key="Watcher.Start">开始 Minecraft 日志监控</sys:String>
<sys:String x:Key="Watcher.WindowLoaded">Minecraft 窗口已加载:{0}{1}</sys:String>
<sys:String x:Key="Watcher.WindowMaximized">已最大化 Minecraft 窗口:{0}</sys:String>
</ResourceDictionary>
@@ -0,0 +1,13 @@
<!-- Formatted by xaml-lang-formatter at 2026-06-28T17:16:52. -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xml:space="preserve">
<!-- Meta -->
<sys:String x:Key="Meta.Code">zh-TW</sys:String>
<sys:String x:Key="Meta.Name">繁體中文(台灣)</sys:String>
</ResourceDictionary>
@@ -0,0 +1,37 @@
namespace PCL.Core.App.Localization;
/// <summary>
/// UI 字体策略。
/// </summary>
public enum LocalizationFontProfile
{
/// <summary>
/// 英文与拉丁文字优先。
/// </summary>
English,
/// <summary>
/// 简体中文标准字形。
/// </summary>
SimplifiedChinese,
/// <summary>
/// 繁体中文标准字形。
/// </summary>
TraditionalChinese,
/// <summary>
/// 日文标准字形。
/// </summary>
Japanese,
/// <summary>
/// 韩文标准字形。
/// </summary>
Korean,
/// <summary>
/// 其他语言。
/// </summary>
Other
}
@@ -0,0 +1,158 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using PCL.Core.App.IoC;
namespace PCL.Core.App.Localization;
/// <summary>
/// 启动器 UI 字体服务。
/// </summary>
public static class LocalizationFontService
{
private const string PclEnglishFont = "./Resources/#PCL English";
private static readonly Uri _ApplicationPackUri = new("pack://application:,,,/");
private static readonly IReadOnlyDictionary<string, LocalizationFontProfile> _ExactCultureProfiles =
new Dictionary<string, LocalizationFontProfile>(StringComparer.OrdinalIgnoreCase)
{
["zh-CN"] = LocalizationFontProfile.SimplifiedChinese,
["zh-SG"] = LocalizationFontProfile.SimplifiedChinese,
["zh-TW"] = LocalizationFontProfile.TraditionalChinese,
["zh-HK"] = LocalizationFontProfile.TraditionalChinese,
["zh-MO"] = LocalizationFontProfile.TraditionalChinese
};
private static readonly IReadOnlyDictionary<string, LocalizationFontProfile> _ScriptProfiles =
new Dictionary<string, LocalizationFontProfile>(StringComparer.OrdinalIgnoreCase)
{
["Hans"] = LocalizationFontProfile.SimplifiedChinese,
["Hant"] = LocalizationFontProfile.TraditionalChinese
};
private static readonly IReadOnlyDictionary<string, LocalizationFontProfile> _LanguageProfiles =
new Dictionary<string, LocalizationFontProfile>(StringComparer.OrdinalIgnoreCase)
{
["zh"] = LocalizationFontProfile.SimplifiedChinese,
["ja"] = LocalizationFontProfile.Japanese,
["ko"] = LocalizationFontProfile.Korean,
["en"] = LocalizationFontProfile.English
};
/// <summary>
/// 生成启动器 UI 使用的 FontFamily。
/// </summary>
/// <param name="customFontName">用户自定义字体。为空时使用当前语言默认字体链。</param>
/// <param name="language">目标语言。为空时使用当前 UI 语言。</param>
public static FontFamily BuildLaunchFontFamily(string? customFontName = null, LocalizationLanguage? language = null)
{
language ??= LocalizationService.CurrentLanguage;
var familyNames = string.IsNullOrWhiteSpace(customFontName)
? _GetDefaultFamilyNames(language.FontProfile)
: _GetCustomFamilyNames(customFontName, language.FontProfile);
return new FontFamily(_ApplicationPackUri, string.Join(", ", familyNames));
}
/// <summary>
/// 生成用于代表某种语言的 FontFamily。
/// </summary>
public static FontFamily BuildRepresentativeFontFamily(LocalizationLanguage language)
{
return BuildRepresentativeFontFamily(language.FontProfile);
}
/// <summary>
/// 生成用于代表某种字体策略的 FontFamily。
/// </summary>
public static FontFamily BuildRepresentativeFontFamily(LocalizationFontProfile profile)
{
return new FontFamily(_ApplicationPackUri, string.Join(", ", _GetDefaultFamilyNames(profile)));
}
/// <summary>
/// 应用启动器 UI 字体。
/// </summary>
public static void ApplyLaunchFont(string? customFontName = null, LocalizationLanguage? language = null)
{
var app = Application.Current ?? Lifecycle.CurrentApplication;
var fontFamily = BuildLaunchFontFamily(customFontName, language);
if (!app.Dispatcher.CheckAccess())
{
app.Dispatcher.Invoke(() => app.Resources["LaunchFontFamily"] = fontFamily);
return;
}
app.Resources["LaunchFontFamily"] = fontFamily;
}
/// <summary>
/// 根据区域性名称解析字体策略。
/// </summary>
public static LocalizationFontProfile ResolveProfileFromCultureName(string? cultureName)
{
var code = _NormalizeCultureCode(cultureName);
if (string.IsNullOrEmpty(code)) return LocalizationFontProfile.Other;
if (_ExactCultureProfiles.TryGetValue(code, out var exactProfile)) return exactProfile;
var cultureParts = code.Split('-', StringSplitOptions.RemoveEmptyEntries);
foreach (var part in cultureParts.Skip(1))
if (_ScriptProfiles.TryGetValue(part, out var scriptProfile))
return scriptProfile;
var languageCode = cultureParts.FirstOrDefault();
return languageCode is not null && _LanguageProfiles.TryGetValue(languageCode, out var languageProfile)
? languageProfile
: LocalizationFontProfile.Other;
}
private static string _NormalizeCultureCode(string? value)
{
return string.IsNullOrWhiteSpace(value)
? string.Empty
: value.Replace('_', '-').Trim();
}
private static IReadOnlyList<string> _GetDefaultFamilyNames(LocalizationFontProfile profile)
{
return profile switch
{
LocalizationFontProfile.English =>
[PclEnglishFont, "Segoe UI", "Microsoft YaHei UI"],
LocalizationFontProfile.SimplifiedChinese =>
[PclEnglishFont, "Microsoft YaHei UI", "Segoe UI"],
LocalizationFontProfile.TraditionalChinese =>
[PclEnglishFont, "Microsoft JhengHei UI", "Microsoft YaHei UI", "Segoe UI"],
LocalizationFontProfile.Japanese =>
[PclEnglishFont, "Yu Gothic UI", "Microsoft YaHei UI", "Segoe UI"],
LocalizationFontProfile.Korean =>
[PclEnglishFont, "Malgun Gothic", "Microsoft YaHei UI", "Segoe UI"],
_ =>
["Segoe UI", "Microsoft YaHei UI"]
};
}
private static IReadOnlyList<string> _GetCustomFamilyNames(string customFontName, LocalizationFontProfile profile)
{
var familyNames = _GetDefaultFamilyNames(profile)
.Where(name => !string.Equals(name, PclEnglishFont, StringComparison.OrdinalIgnoreCase))
.ToList();
familyNames.Insert(0, _EscapeFontFamilyName(customFontName));
return familyNames.Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
}
private static string _EscapeFontFamilyName(string name)
{
return name.Replace(",", ",,");
}
}
@@ -0,0 +1,101 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace PCL.Core.App.Localization;
/// <summary>
/// <p>将 XAML 绑定值按照当前展示区域性格式化为字符串。</p>
/// <p>
/// 该转换器用于界面绑定中的数字、日期、时间等 <see cref="IFormattable" /> 值展示。
/// 格式化时使用 <see cref="CultureInfo.CurrentCulture" />
/// 因此会跟随 <see cref="LocalizationService" /> 当前设置的展示格式区域性。
/// </p>
/// <p>
/// 该转换器只处理“值的显示格式”,例如数字分隔符、日期顺序、时间格式等。
/// 它不负责翻译文本、不负责切换语言资源,也不应参与配置、日志、协议、缓存键等
/// 需要稳定格式的内容生成。
/// </p>
/// <p>
/// 使用时需要通过 <c>ConverterParameter</c> 提供标准 .NET 格式字符串。
/// 如果未提供格式字符串,则返回原始绑定值。
/// </p>
/// </summary>
/// <example>
/// XAML 示例:
/// <code>
/// <![CDATA[
/// <TextBlock
/// Text="{Binding UpdateTime,
/// Converter={StaticResource LocalizationFormatConverter},
/// ConverterParameter=G}" />
///
/// <TextBlock
/// Text="{Binding DownloadSpeed,
/// Converter={StaticResource LocalizationFormatConverter},
/// ConverterParameter=N2}" />
/// ]]>
/// </code>
/// </example>
public sealed class LocalizationFormatConverter : IValueConverter
{
/// <summary>
/// 将绑定值按照当前展示区域性格式化。
/// </summary>
/// <param name="value">
/// 需要格式化的绑定值。通常是数字、日期、时间等实现了 <see cref="IFormattable" /> 的类型。
/// </param>
/// <param name="targetType">
/// 绑定目标属性的类型。本转换器不依赖该参数。
/// </param>
/// <param name="parameter">
/// 标准 .NET 格式字符串,例如 <c>N2</c>、<c>G</c>、<c>d</c>。
/// 如果该参数不是有效字符串,或为空白字符串,则直接返回原始值。
/// </param>
/// <param name="culture">
/// WPF 绑定传入的区域性。本转换器不直接使用该参数,
/// 而是统一使用 <see cref="CultureInfo.CurrentCulture" />
/// 以确保格式化结果跟随应用当前展示格式区域性。
/// </param>
/// <returns>
/// 如果 <paramref name="value" /> 为 <see langword="null" />,返回 <see langword="null" />
/// 如果未提供格式字符串,返回原始值;
/// 否则返回使用当前展示区域性格式化后的字符串。
/// </returns>
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is null) return null;
if (parameter is not string format || string.IsNullOrWhiteSpace(format)) return value;
return value switch
{
IFormattable formattable => formattable.ToString(format, Lang.Culture),
_ => string.Format(Lang.Culture, "{0}", value)
};
}
/// <summary>
/// 不支持从格式化后的显示文本反向转换回原始值。
/// </summary>
/// <param name="value">
/// 绑定目标传回的值。
/// </param>
/// <param name="targetType">
/// 绑定源目标类型。
/// </param>
/// <param name="parameter">
/// 转换参数。
/// </param>
/// <param name="culture">
/// WPF 绑定传入的区域性。
/// </param>
/// <returns>
/// 该方法不会返回值。
/// </returns>
/// <exception cref="NotSupportedException">
/// 始终抛出。该转换器仅用于单向显示格式化。
/// </exception>
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
@@ -0,0 +1,15 @@
namespace PCL.Core.App.Localization;
/// <summary>
/// 表示一个受支持的 UI 语言。
/// </summary>
/// <param name="Code">语言配置值。</param>
/// <param name="NativeName">语言的本地名称。</param>
/// <param name="CultureName">用于 <see cref="System.Globalization.CultureInfo" /> 的区域性名称。</param>
/// <param name="FontProfile"> 语言对应的 UI 字体策略。
/// </param>
public sealed record LocalizationLanguage(
string Code,
string NativeName,
string CultureName,
LocalizationFontProfile FontProfile);
@@ -0,0 +1,322 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Windows;
using PCL.Core.App.Configuration;
using PCL.Core.App.IoC;
namespace PCL.Core.App.Localization;
/// <summary>
/// UI 本地化服务。
/// </summary>
[LifecycleScope("localization", "本地化", false)]
[LifecycleService(LifecycleState.Loaded, Priority = 114514)]
public sealed partial class LocalizationService
{
/// <summary>
/// 跟随系统设置的配置值。
/// </summary>
public const string Auto = "auto";
/// <summary>
/// 展示格式同步 UI 语言的配置值。
/// </summary>
public const string FormatCultureFollowLanguage = "ui-language";
/// <summary>
/// 默认语言,也是语言资源的完整兜底。
/// </summary>
public const string DefaultLanguageCode = "zh-CN";
private const string AssemblyResourcePrefix = "/PCL.Core;component/App/Localization/Languages/";
private static readonly LocalizationLanguage _DefaultLanguage = new(
DefaultLanguageCode,
"简体中文(中国大陆)",
"zh-CN",
LocalizationFontProfile.SimplifiedChinese);
private static ResourceDictionary? _baseLanguageDictionary;
private static ResourceDictionary? _currentLanguageDictionary;
private static CultureInfo _systemFormatCulture = CultureInfo.CurrentCulture;
private static CultureInfo _systemUiCulture = CultureInfo.CurrentUICulture;
/// <summary>
/// 当前 UI 语言。
/// </summary>
public static LocalizationLanguage CurrentLanguage { get; private set; } = _DefaultLanguage;
/// <summary>
/// 当前 UI 展示格式所使用的区域性。
/// </summary>
public static CultureInfo CurrentFormatCulture { get; private set; } = CultureInfo.CurrentCulture;
/// <summary>
/// 受支持的 UI 语言。
/// </summary>
public static IReadOnlyList<LocalizationLanguage> SupportedLanguages { get; } =
[
_DefaultLanguage,
new("zh-TW", "繁體中文(台灣)", "zh-TW", LocalizationFontProfile.TraditionalChinese),
new("en-US", "English (US)", "en-US", LocalizationFontProfile.English),
new("en-GB", "English (United Kingdom)", "en-GB", LocalizationFontProfile.English),
new("ja-JP", "日本語(日本)", "ja-JP", LocalizationFontProfile.Japanese),
new("fr-FR", "Français (France)", "fr-FR", LocalizationFontProfile.Other),
new("es-ES", "Español (España)", "es-ES", LocalizationFontProfile.Other)
];
[RegisterConfigEvent]
public static ConfigEventRegistry OnLanguageConfigChanged => new(
[
Config.Preference.Localization.LanguageConfig,
Config.Preference.Localization.FormatCultureConfig
],
trigger: ConfigEvent.Update,
handler: _ => ApplyFromConfig()
);
/// <summary>
/// 语言或展示格式更改后触发。
/// </summary>
public static event Action? LanguageChanged;
[LifecycleStart]
private static void _Start()
{
_systemFormatCulture = CultureInfo.CurrentCulture;
_systemUiCulture = CultureInfo.CurrentUICulture;
ApplyFromConfig();
}
/// <summary>
/// 按当前配置应用 UI 语言与展示格式。
/// </summary>
public static void ApplyFromConfig(bool save = false)
{
if (!ConfigService.IsInitialized)
{
Apply(Auto, Auto, false);
return;
}
Apply(
Config.Preference.Localization.Language,
Config.Preference.Localization.FormatCulture,
save);
}
/// <summary>
/// 应用 UI 语言与展示格式。
/// </summary>
/// <param name="languageCode">UI 语言代码,auto 表示跟随系统语言。</param>
/// <param name="formatCultureCode">展示格式区域性,auto 表示跟随系统区域格式。</param>
/// <param name="save">是否写回配置。</param>
public static void Apply(string languageCode, string formatCultureCode = Auto, bool save = true)
{
var normalizedLanguageCode = _NormalizeConfigValue(languageCode);
var language = ResolveLanguage(normalizedLanguageCode);
var uiCulture = CultureInfo.GetCultureInfo(language.CultureName);
var formatCulture = _ResolveFormatCulture(formatCultureCode, uiCulture, out var normalizedFormatCultureCode);
var isLanguageChanged = !string.Equals(CurrentLanguage.Code, language.Code, StringComparison.OrdinalIgnoreCase);
var isFormatCultureChanged = !string.Equals(CurrentFormatCulture.Name, formatCulture.Name,
StringComparison.OrdinalIgnoreCase);
if (_baseLanguageDictionary is not null && !isLanguageChanged && !isFormatCultureChanged)
{
_SaveConfigIfNeeded(save, normalizedLanguageCode, language, normalizedFormatCultureCode);
return;
}
_ApplyCultures(uiCulture, formatCulture);
_ApplyLanguageResources(language.Code, uiCulture, formatCulture);
CurrentLanguage = language;
LocalizationFontService.ApplyLaunchFont(
ConfigService.IsInitialized ? Config.Preference.Font : null,
language);
CurrentFormatCulture = formatCulture;
_SaveConfigIfNeeded(save, normalizedLanguageCode, language, normalizedFormatCultureCode);
_LogInfo($"当前 UI 语言: {language.Code}, 展示格式: {formatCulture.Name}");
LanguageChanged?.Invoke();
}
/// <summary>
/// 判断语言代码是否受支持。
/// </summary>
public static bool IsLanguageSupported(string? languageCode)
{
if (string.IsNullOrWhiteSpace(languageCode)) return true;
if (string.Equals(languageCode, Auto, StringComparison.OrdinalIgnoreCase)) return true;
var normalizedCode = _NormalizeCultureCode(languageCode);
return SupportedLanguages.Any(language =>
string.Equals(language.Code, normalizedCode, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// 按配置值解析语言。
/// </summary>
public static LocalizationLanguage ResolveLanguage(string? languageCode)
{
if (string.IsNullOrWhiteSpace(languageCode) ||
string.Equals(languageCode, Auto, StringComparison.OrdinalIgnoreCase)) return _ResolveSystemLanguage();
var normalizedCode = _NormalizeCultureCode(languageCode);
return SupportedLanguages.FirstOrDefault(language =>
string.Equals(language.Code, normalizedCode, StringComparison.OrdinalIgnoreCase))
?? _DefaultLanguage;
}
private static void _SaveConfigIfNeeded(bool save, string normalizedLanguageCode, LocalizationLanguage language,
string normalizedFormatCultureCode)
{
if (!save || !ConfigService.IsInitialized) return;
var configLanguageCode = string.Equals(normalizedLanguageCode, Auto, StringComparison.OrdinalIgnoreCase)
? Auto
: language.Code;
if (Config.Preference.Localization.Language != configLanguageCode)
Config.Preference.Localization.Language = configLanguageCode;
if (Config.Preference.Localization.FormatCulture != normalizedFormatCultureCode)
Config.Preference.Localization.FormatCulture = normalizedFormatCultureCode;
}
private static LocalizationLanguage _ResolveSystemLanguage()
{
var systemLanguage = _NormalizeCultureCode(_systemUiCulture.Name);
var exact = SupportedLanguages.FirstOrDefault(language =>
string.Equals(language.Code, systemLanguage, StringComparison.OrdinalIgnoreCase));
if (exact is not null) return exact;
var neutral = _systemUiCulture.TwoLetterISOLanguageName;
return SupportedLanguages.FirstOrDefault(language =>
language.Code.StartsWith(neutral + "-", StringComparison.OrdinalIgnoreCase))
?? _DefaultLanguage;
}
private static CultureInfo _ResolveFormatCulture(string? formatCultureCode, CultureInfo uiCulture,
out string normalizedCode)
{
if (string.IsNullOrWhiteSpace(formatCultureCode) ||
string.Equals(formatCultureCode, Auto, StringComparison.OrdinalIgnoreCase))
{
normalizedCode = Auto;
return _systemFormatCulture;
}
if (string.Equals(formatCultureCode, FormatCultureFollowLanguage, StringComparison.OrdinalIgnoreCase))
{
normalizedCode = FormatCultureFollowLanguage;
return uiCulture;
}
try
{
var culture = CultureInfo.GetCultureInfo(formatCultureCode);
normalizedCode = culture.Name;
return culture;
}
catch (CultureNotFoundException)
{
_LogWarn($"无法识别展示格式区域性: {formatCultureCode},已回退系统区域格式");
normalizedCode = Auto;
return _systemFormatCulture;
}
}
private static void _ApplyCultures(CultureInfo uiCulture, CultureInfo formatCulture)
{
CultureInfo.CurrentUICulture = uiCulture;
CultureInfo.DefaultThreadCurrentUICulture = uiCulture;
Thread.CurrentThread.CurrentUICulture = uiCulture;
CultureInfo.CurrentCulture = formatCulture;
CultureInfo.DefaultThreadCurrentCulture = formatCulture;
Thread.CurrentThread.CurrentCulture = formatCulture;
Lang.SyncCulture(formatCulture);
}
private static void _ApplyLanguageResources(string languageCode, CultureInfo uiCulture, CultureInfo formatCulture)
{
var app = Application.Current ?? Lifecycle.CurrentApplication;
if (!app.Dispatcher.CheckAccess())
{
app.Dispatcher.Invoke(() =>
{
_ApplyCultures(uiCulture, formatCulture);
_ApplyLanguageResourcesCore(app, languageCode);
});
return;
}
_ApplyLanguageResourcesCore(app, languageCode);
}
private static void _ApplyLanguageResourcesCore(Application app, string languageCode)
{
var dictionaries = app.Resources.MergedDictionaries;
if (_baseLanguageDictionary is not null) dictionaries.Remove(_baseLanguageDictionary);
if (_currentLanguageDictionary is not null) dictionaries.Remove(_currentLanguageDictionary);
_baseLanguageDictionary = _LoadLanguageDictionary(DefaultLanguageCode);
dictionaries.Add(_baseLanguageDictionary);
if (string.Equals(languageCode, DefaultLanguageCode, StringComparison.OrdinalIgnoreCase))
{
_currentLanguageDictionary = null;
}
else
{
_currentLanguageDictionary = _LoadLanguageDictionary(languageCode);
dictionaries.Add(_currentLanguageDictionary);
}
}
private static ResourceDictionary _LoadLanguageDictionary(string languageCode)
{
return new ResourceDictionary
{
Source = new Uri($"{AssemblyResourcePrefix}{languageCode}.xaml", UriKind.Relative)
};
}
private static string _NormalizeConfigValue(string? value)
{
return string.IsNullOrWhiteSpace(value) ? Auto : _NormalizeCultureCode(value);
}
private static string _NormalizeCultureCode(string? value)
{
return string.IsNullOrWhiteSpace(value) ? Auto : value.Replace('_', '-').Trim();
}
private static void _LogInfo(string message)
{
try
{
Context.Info(message);
}
catch (InvalidOperationException)
{
// 静态 API 可在生命周期服务创建前被测试调用,此时无需写入生命周期日志。
}
}
private static void _LogWarn(string message)
{
try
{
Context.Warn(message);
}
catch (InvalidOperationException)
{
// 静态 API 可在生命周期服务创建前被测试调用,此时无需写入生命周期日志。
}
}
}
@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using PCL.Core.Logging;
namespace PCL.Core.App.Localization;
public static class WeakLanguageChanged
{
private static readonly object _Lock = new();
private static readonly List<(WeakReference<object> Target, Action<object> Handler)> _Handlers = [];
private static bool _hooked;
public static void Add<T>(T target, Action<T> handler) where T : class
{
if (target is null) throw new ArgumentNullException(nameof(target));
if (handler is null) throw new ArgumentNullException(nameof(handler));
if (handler.Target is not null)
throw new ArgumentException(
"handler 必须是不捕获实例的静态委托,请改用形如 static t => t.Foo() 的写法并通过 target 传入实例。",
nameof(handler));
lock (_Lock)
{
if (!_hooked)
{
LocalizationService.LanguageChanged += _OnLanguageChanged;
_hooked = true;
}
_Handlers.Add((new WeakReference<object>(target), o => handler((T)o)));
}
}
private static void _OnLanguageChanged()
{
(WeakReference<object> Target, Action<object> Handler)[] snapshot;
lock (_Lock)
{
_Handlers.RemoveAll(h => !h.Target.TryGetTarget(out _));
snapshot = _Handlers.ToArray();
}
foreach (var (target, handler) in snapshot)
{
if (!target.TryGetTarget(out var t))
continue;
try
{
handler(t);
}
catch (Exception ex)
{
LogWrapper.Warn(ex, "WeakLanguageChanged", "语言变更处理器执行出错");
}
}
}
}
+89
View File
@@ -0,0 +1,89 @@
using System.Text.Json.Serialization;
using PCL.Core.Utils.OS;
namespace PCL.Core.App;
/// <summary>
/// 启动器版本信息模型
/// </summary>
/// <param name="BaseVersion">基本版本号, 如 <c>2.14.1</c></param>
/// <param name="Suffix">后缀, 如 <c>beta.1</c></param>
/// <param name="Code">内部版本代号, 如 <c>712</c></param>
/// <param name="UpstreamVersion">上游版本, 如 <c>2.12.0</c></param>
public sealed record LauncherVersionModel(
[property: JsonPropertyName("base")] string BaseVersion,
[property: JsonPropertyName("suffix")] string Suffix,
[property: JsonPropertyName("code")] int Code,
[property: JsonPropertyName("upstream")] string UpstreamVersion
) {
private static readonly (string Name, int Code) _CompilationBranchInfo =
#if DEBUG
("Debug", 100);
#elif CI
("CI", 50);
#else
("Publish", 0);
#endif
private static readonly string? _SecretCommitInfo = EnvironmentInterop.GetSecret("GITHUB_SHA", false);
/// <summary>
/// 基本版本名, 若 <see cref="Suffix"/> 存在实际值会附加后缀, 否则与 <see cref="BaseVersion"/> 相同
/// </summary>
public string BaseName { get; } = BaseVersion + (string.IsNullOrWhiteSpace(Suffix) ? "" : "-" + Suffix);
/// <summary>
/// 发行分支名
/// </summary>
public string BranchName { get; } = _CompilationBranchInfo.Name;
/// <summary>
/// 发行分支代号
/// </summary>
public int BranchCode { get; } = _CompilationBranchInfo.Code;
/// <summary>
/// 标准版本号
/// </summary>
public string StandardVersion { get; } = BaseVersion + "." + _CompilationBranchInfo.Code;
/// <summary>
/// 代码提交版本 hash, 若不存在 (非 CI 构建) 则为 <c>native</c>
/// </summary>
public string Commit { get; } = _SecretCommitInfo ?? "native";
/// <summary>
/// 代码提交版本 hash 的摘要 (取前 7 位), 若不存在 (非 CI 构建) 则为 <c>native</c>
/// </summary>
public string CommitDigest { get; } = _SecretCommitInfo?[..7] ?? "native";
public override string ToString()
{
return $"{BranchName} {BaseName} ({Code}, {CommitDigest})";
}
}
/// <summary>
/// 第三方内容许可信息模型
/// </summary>
/// <param name="Name">内容名称</param>
/// <param name="Information">许可信息</param>
/// <param name="WebsiteUri">网站 URI</param>
/// <param name="LicenseUri">许可证 URI</param>
public sealed record ThirdPartyLicenseModel(
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("info")] string Information,
[property: JsonPropertyName("website")] string? WebsiteUri = null,
[property: JsonPropertyName("license")] string? LicenseUri = null
);
/// <summary>
/// 启动器元数据模型
/// </summary>
/// <param name="Name">程序名称</param>
/// <param name="Version">版本信息</param>
public sealed record MetadataModel(
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("version")] LauncherVersionModel Version,
[property: JsonPropertyName("licenses")] ThirdPartyLicenseModel[] Licenses
);
+92
View File
@@ -0,0 +1,92 @@
using System;
using System.IO;
using PCL.Core.Utils.OS;
using Special = System.Environment.SpecialFolder;
namespace PCL.Core.App;
/// <summary>
/// Application global paths.<br/>
/// <b>NOTE</b>: The behaviors of all path strings depends on <see cref="Path"/> API provided by
/// .NET standard library. You should use <see cref="Path"/> and other APIs relative to it to process
/// any path string from this service, rather than concat paths manually.
/// </summary>
public static class Paths
{
/// <summary>
/// The default directory used for relative path combining.
/// </summary>
public static string DefaultDirectory => Basics.ExecutableDirectory;
private static string _data;
private static string _sharedData;
private static string _sharedLocalData;
private static string _temp;
/// <summary>
/// Per-instance data directory.
/// </summary>
public static string Data { get => _data; set => _data = value; }
/// <summary>
/// Shared synchronized data directory.
/// </summary>
public static string SharedData { get => _sharedData; set => _sharedData = value; }
/// <summary>
/// Shared synchronized data directory of old versions.<br/>
/// Keep the value just for migration, DO NOT USE IT.
/// </summary>
public static string OldSharedData { get; set; }
/// <summary>
/// Shared local data directory, used to put some large files that can be released or downloaded back anytime.
/// </summary>
public static string SharedLocalData { get => _sharedLocalData; set => _sharedLocalData = value; }
/// <summary>
/// Temporary files directory (can be deleted anytime, except when the program is running).
/// </summary>
public static string Temp { get => _temp; set => _temp = value; }
/// <summary>
/// Get path string relative to a special folder.
/// </summary>
/// <param name="folder">the special folder</param>
/// <param name="relative">the relative path</param>
/// <returns>the path string relative to the special folder</returns>
public static string GetSpecialPath(Special folder, string relative)
{
var folderPath = Environment.GetFolderPath(folder);
return Path.Combine(folderPath, relative);
}
static Paths()
{
#if DEBUG
const string name = "PCLCE_Debug";
const string oldName = ".PCLCEDebug";
#else
const string name = "PCLCE";
const string oldName = ".PCLCE";
#endif
// fill paths
_data = Path.Combine(DefaultDirectory, "PCL");
_sharedData = GetSpecialPath(Special.ApplicationData, name);
_sharedLocalData = GetSpecialPath(Special.LocalApplicationData, name);
_temp = Path.Combine(Path.GetTempPath(), name);
OldSharedData = GetSpecialPath(Special.ApplicationData, oldName);
#if DEBUG
// read environment variables
EnvironmentInterop.ReadVariable("PCL_PATH", ref _data);
EnvironmentInterop.ReadVariable("PCL_PATH_SHARED", ref _sharedData);
EnvironmentInterop.ReadVariable("PCL_PATH_LOCAL", ref _sharedLocalData);
EnvironmentInterop.ReadVariable("PCL_PATH_TEMP", ref _temp);
#endif
// create directories
Directory.CreateDirectory(_data);
Directory.CreateDirectory(_sharedData);
Directory.CreateDirectory(_sharedLocalData);
Directory.CreateDirectory(_temp);
}
}
+43
View File
@@ -0,0 +1,43 @@
using PCL.Core.Utils.Exts;
using PCL.Core.Utils.OS;
namespace PCL.Core.App;
// ReSharper disable InconsistentNaming
public static class Secrets
{
/// <summary>
/// 微软 OAuth 的 Client ID
/// </summary>
public static string MSOAuthClientId { get; } = EnvironmentInterop.GetSecret("MS_CLIENT_ID", readEnvDebugOnly: true).ReplaceNullOrEmpty();
/// <summary>
/// CurseForge API 的 Client ID
/// </summary>
public static string CurseForgeAPIKey { get; } = EnvironmentInterop.GetSecret("CURSEFORGE_API_KEY", readEnvDebugOnly: true).ReplaceNullOrEmpty();
/// <summary>
/// 遥测密钥
/// </summary>
public static string TelemetryKey { get; } = EnvironmentInterop.GetSecret("TELEMETRY_KEY", readEnvDebugOnly: true).ReplaceNullOrEmpty();
/// <summary>
/// Natayark ID OAuth 的 Client ID
/// </summary>
public static string NatayarkClientId { get; } = EnvironmentInterop.GetSecret("NAID_CLIENT_ID", readEnvDebugOnly: true).ReplaceNullOrEmpty();
/// <summary>
/// Natayark ID OAuth 的 Client ID
/// </summary>
public static string NatayarkClientSecret { get; } = EnvironmentInterop.GetSecret("NAID_CLIENT_SECRET", readEnvDebugOnly: true).ReplaceNullOrEmpty();
/// <summary>
/// 联机根服务器
/// </summary>
public static string[] LinkServers { get; } = EnvironmentInterop.GetSecret("LINK_SERVER_ROOT", readEnvDebugOnly: true).ReplaceNullOrEmpty().Split("|");
/// <summary>
/// 当前版本的 Git 提交 SHA
/// </summary>
public static string CommitHash { get; } = EnvironmentInterop.GetSecret("GITHUB_SHA", readEnvDebugOnly: true).ReplaceNullOrEmpty();
}
+356
View File
@@ -0,0 +1,356 @@
using System.Collections.Generic;
using PCL.Core.App.Configuration;
namespace PCL.Core.App;
/// <summary>
/// 全局状态类。
/// </summary>
// ReSharper disable InconsistentNaming
public static partial class States
{
/// <summary>
/// 唯一标识符。
/// </summary>
[ConfigItem<string>("Identify", "")] public static partial string Identifier { get; set; }
/// <summary>
/// 自定义变量。
/// </summary>
[AnyConfigItem<Dictionary<string, string>>("CustomVariables", ConfigSource.Local)] public static partial Dictionary<string, string> CustomVariables { get; set; }
/// <summary>
/// 提示状态。
/// </summary>
[ConfigGroup("Hint")] partial class HintStatesGroup
{
/// <summary>
/// 过大下载线程警告。
/// </summary>
[ConfigItem<bool>("HintDownloadThread", false)] public partial bool LargeDownloadThread { get; set; }
// [ConfigItem<int>("HintNotice", 0)] public partial int Notice { get; set; }
/// <summary>
/// 渲染器选择提示。
/// </summary>
[ConfigItem<bool>("HintRenderer", false)] public partial bool Renderer { get; set; }
/// <summary>
/// 使用调试级别 Log4j2 配置提示。
/// </summary>
[ConfigItem<bool>("HintDebugLog4j2Config", false)] public partial bool DebugLog4j2Config { get; set; }
// [ConfigItem<int>("HintDownload", 0)] public partial int Download { get; set; }
/// <summary>
/// 单击 Minecraft 返回游戏版本选择提示。
/// </summary>
[ConfigItem<bool>("HintInstallBack", false)] public partial bool InstallPageBack { get; set; }
/// <summary>
/// 隐藏实例提示。
/// </summary>
[ConfigItem<bool>("HintHide", false)] public partial bool HideGameInstance { get; set; }
/// <summary>
/// 手动安装版本提示。
/// </summary>
[ConfigItem<bool>("HintHandInstall", false)] public partial bool ManualInstall { get; set; }
/// <summary>
/// 清理垃圾提示。
/// </summary>
[ConfigItem<int>("HintClearRubbish", 0)] public partial int CleanJunkFile { get; set; }
/// <summary>
/// Mod 更新提示。
/// </summary>
[ConfigItem<bool>("HintUpdateMod", false)] public partial bool UpdateMod { get; set; }
/// <summary>
/// 执行自定义主页内含命令提示。
/// </summary>
[ConfigItem<bool>("HintCustomCommand", false)] public partial bool HomepageCommand { get; set; }
/// <summary>
/// 非信任主页警告。
/// </summary>
[ConfigItem<bool>("HintCustomWarn", false)] public partial bool UntrustedHomepage { get; set; }
/// <summary>
/// 全局设置中在实例独立设置中寻找更多高级启动选项的提示。
/// </summary>
[ConfigItem<bool>("HintMoreAdvancedSetup", false)] public partial bool MoreInstanceSetup { get; set; }
/// <summary>
/// 进入实例独立设置提示。
/// </summary>
[ConfigItem<bool>("HintIndieSetup", false)] public partial bool IndieSetup { get; set; }
/// <summary>
/// 选择档案以启动游戏提示。
/// </summary>
[ConfigItem<bool>("HintProfileSelect", false)] public partial bool LaunchWithProfile { get; set; }
/// <summary>
/// 导出配置提示。
/// </summary>
[ConfigItem<bool>("HintExportConfig", false)] public partial bool ExportConfig { get; set; }
/// <summary>
/// 实时日志最大行数提示。
/// </summary>
[ConfigItem<bool>("HintMaxLog", false)] public partial bool MaxGameLog { get; set; }
/// <summary>
/// 非 ASCII 路径警告。
/// </summary>
[ConfigItem<bool>("HintDisableGamePathCheckTip", false)] public partial bool NonAsciiGamePath { get; set; }
/// <summary>
/// 启动时的社区版提示。
/// </summary>
[ConfigItem<bool>("UiLauncherCEHint", true)] public partial bool CEMessage { get; set; }
/// <summary>
/// 投影管理首次使用提示。
/// </summary>
[ConfigItem<bool>("UiSchematicFirstTimeHintShown", false)] public partial bool SchematicFirstTime { get; set; }
/// <summary>
/// 已显示的公告。
/// </summary>
[ConfigItem<string>("SystemSystemAnnouncement", "")] public partial string ShowedAnnouncements { get; set; }
/// <summary>
/// 数据包更新警告。
/// </summary>
[ConfigItem<bool>("HintDatapackUpdate",false)] public partial bool FunctionDatapackUpdate { get; set; }
}
/// <summary>
/// 游戏与启动相关状态。
/// </summary>
[ConfigGroup("Game")] partial class GameStatesGroup
{
/// <summary>
/// Java 列表版本。
/// </summary>
[ConfigItem<int>("CacheJavaListVersion", 0)] public partial int JavaListVersion { get; set; }
/// <summary>
/// MC 版本 Drops。
/// </summary>
[ConfigItem<string>("CacheDrops", "")] public partial string Drops { get; set; }
/// <summary>
/// 当前实例。
/// </summary>
[ConfigItem<string>("LaunchInstanceSelect", "", ConfigSource.Local)] public partial string SelectedInstance { get; set; }
/// <summary>
/// 当前文件夹。
/// </summary>
[ConfigItem<string>("LaunchFolderSelect", "", ConfigSource.Local)] public partial string SelectedFolder { get; set; }
/// <summary>
/// 所有文件夹。
/// </summary>
[ConfigItem<string>("LaunchFolders", "")] public partial string Folders { get; set; }
/// <summary>
/// 已知所有 Java 实例。
/// </summary>
[ConfigItem<string>("LaunchArgumentJavaUser", "[]")] public partial string JavaList { get; set; }
/// <summary>
/// 收藏的社区资源
/// </summary>
[ConfigItem<string>("CompFavorites", "[]")] public partial string CompFavorites { get; set; }
}
/// <summary>
/// 用户界面状态。
/// </summary>
[ConfigGroup("UI")] partial class UiStatesGroup
{
/// <summary>
/// 当前自定义主页 URL。
/// </summary>
[ConfigItem<string>("CacheSavedPageUrl", "")] public partial string SavedHomepageUrl { get; set; }
/// <summary>
/// 当前自定义主页版本。
/// </summary>
[ConfigItem<string>("CacheSavedPageVersion", "")] public partial string SavedHomepageVersion { get; set; }
/// <summary>
/// 窗口高度。
/// </summary>
[ConfigItem<double>("WindowHeight", 550, ConfigSource.Local)] public partial double WindowHeight { get; set; }
/// <summary>
/// 窗口宽度。
/// </summary>
[ConfigItem<double>("WindowWidth", 900, ConfigSource.Local)] public partial double WindowWidth { get; set; }
}
/// <summary>
/// 系统状态。
/// </summary>
[ConfigGroup("System")] partial class SystemStatesGroup
{
/// <summary>
/// 识别码。
/// </summary>
[ConfigItem<string>("LaunchUuid", "")] public partial string LaunchUuid { get; set; }
/// <summary>
/// 上次导出配置路径。
/// </summary>
[ConfigItem<string>("CacheExportConfig", "")] public partial string ExportConfigPath { get; set; }
/// <summary>
/// 最终用户许可协议。
/// </summary>
[ConfigItem<bool>("SystemEula", false)] public partial bool LauncherEula { get; set; }
/// <summary>
/// 启动器打开次数。
/// </summary>
[ConfigItem<int>("SystemCount", 0, ConfigSource.SharedEncrypt)] public partial int StartupCount { get; set; }
/// <summary>
/// 游戏启动次数。
/// </summary>
[ConfigItem<int>("SystemLaunchCount", 0, ConfigSource.SharedEncrypt)] public partial int LaunchCount { get; set; }
/// <summary>
/// 上个版本。
/// </summary>
[ConfigItem<int>("SystemLastVersionReg", 0, ConfigSource.SharedEncrypt)] public partial int LastVersion { get; set; }
// [ConfigItem<int>("SystemHighestSavedBetaVersionReg", 0, ConfigSource.SharedEncrypt)] public partial int LastSavedBetaVersion { get; set; }
/// <summary>
/// 上个最高 Beta 版本。
/// </summary>
[ConfigItem<int>("SystemHighestBetaVersionReg", 0, ConfigSource.SharedEncrypt)] public partial int LastBetaVersion { get; set; }
/// <summary>
/// 上个最高 Alpha 版本。
/// </summary>
[ConfigItem<int>("SystemHighestAlphaVersionReg", 0, ConfigSource.SharedEncrypt)] public partial int LastAlphaVersion { get; set; }
/// <summary>
/// 全局配置版本。
/// </summary>
[ConfigItem<int>("SystemSetupVersionReg", 1)] public partial int SetupVersionGlobal { get; set; }
/// <summary>
/// 本地配置版本。
/// </summary>
[ConfigItem<int>("SystemSetupVersionIni", 1, ConfigSource.Local)] public partial int SetupVersionLocal { get; set; }
/// <summary>
/// 启动器公告。
/// </summary>
[ConfigItem<int>("SystemSystemActivity", 0, ConfigSource.Local)] public partial int AnnounceSolution { get; set; }
}
/// <summary>
/// 工具状态。
/// </summary>
[ConfigGroup("Tool")] partial class ToolStatesGroup
{
/// <summary>
/// 自定义下载目标目录。
/// </summary>
[ConfigItem<string>("CacheDownloadFolder", "")] public partial string DownloadFolder { get; set; }
/// <summary>
/// 自定义下载 UA。
/// </summary>
[ConfigItem<string>("ToolDownloadCustomUserAgent", "")] public partial string DownloadUserAgent { get; set; }
/// <summary>
/// 最新 MC 正式版
/// </summary>
[ConfigItem<string>("ToolUpdateReleaseLast", "")] public partial string LastRelease { get; set; }
/// <summary>
/// 最新 MC 快照版
/// </summary>
[ConfigItem<string>("ToolUpdateSnapshotLast", "")] public partial string LastSnapshot { get; set; }
}
/// <summary>
/// 联机大厅状态。
/// </summary>
[ConfigGroup("Link")] partial class LinkStatesGroup
{
/// <summary>
/// 大厅最终用户许可协议。
/// </summary>
[ConfigItem<bool>("LinkEula", false)] public partial bool LinkEula { get; set; }
/// <summary>
/// 公告缓存。
/// </summary>
[ConfigItem<string>("LinkAnnounceCache", "", ConfigSource.SharedEncrypt)] public partial string AnnounceCache { get; set; }
/// <summary>
/// 公告缓存版本。
/// </summary>
[ConfigItem<int>("LinkAnnounceCacheVer", 0)] public partial int AnnounceCacheVer { get; set; }
/// <summary>
/// Natayark ID 刷新令牌。
/// </summary>
[ConfigItem<string>("LinkNaidRefreshToken", "", ConfigSource.SharedEncrypt)] public partial string NaidRefreshToken { get; set; }
/// <summary>
/// Natayark ID 令牌过期时间。
/// </summary>
[ConfigItem<string>("LinkNaidRefreshExpiresAt", "", ConfigSource.SharedEncrypt)] public partial string NaidRefreshExpireTime { get; set; }
/// <summary>
/// 首次网络测试状态。
/// </summary>
[ConfigItem<bool>("LinkFirstTimeNetTest", true, ConfigSource.SharedEncrypt)] public partial bool DoFirstTimeNetTest { get; set; }
}
/// <summary>
/// 实例独立状态
/// </summary>
[ConfigGroup("Instance", ConfigSource.GameInstance)] partial class InstanceStatesGroup
{
[ConfigItem<int>("VersionLaunchCount", 0)] public partial ArgConfig<int> LaunchCount { get; }
[ConfigItem<bool>("IsStar", false)] public partial ArgConfig<bool> Starred { get; }
[ConfigItem<int>("DisplayType", 0)] public partial ArgConfig<int> CardType { get; }
[ConfigItem<string>("Logo", "")] public partial ArgConfig<string> LogoPath { get; }
[ConfigItem<bool>("LogoCustom", false)] public partial ArgConfig<bool> IsLogoCustom { get; }
[ConfigItem<string>("CustomInfo", "")] public partial ArgConfig<string> CustomInfo { get; }
[ConfigItem<string>("Info", "")] public partial ArgConfig<string> Info { get; }
[ConfigItem<string>("ReleaseTime", "")] public partial ArgConfig<string> ReleaseTime { get; }
[ConfigItem<int>("State", 0)] public partial ArgConfig<int> State { get; }
[ConfigItem<string>("VersionFabric", "")] public partial ArgConfig<string> FabricVersion { get; }
[ConfigItem<string>("VersionLegacyFabric", "")] public partial ArgConfig<string> LegacyFabricVersion { get; }
[ConfigItem<string>("VersionQuilt", "")] public partial ArgConfig<string> QuiltVersion { get; }
[ConfigItem<string>("VersionLabyMod", "")] public partial ArgConfig<string> LabyModVersion { get; }
[ConfigItem<string>("VersionOptiFine", "")] public partial ArgConfig<string> OptiFineVersion { get; }
[ConfigItem<bool>("VersionLiteLoader", false)] public partial ArgConfig<bool> HasLiteLoader { get; }
[ConfigItem<string>("VersionForge", "")] public partial ArgConfig<string> ForgeVersion { get; }
[ConfigItem<string>("VersionNeoForge", "")] public partial ArgConfig<string> NeoForgeVersion { get; }
[ConfigItem<string>("VersionCleanroom", "")] public partial ArgConfig<string> CleanroomVersion { get; }
[ConfigItem<string>("VersionVanillaName", "Unknown")] public partial ArgConfig<string> VanillaVersionName { get; }
[ConfigItem<string>("VersionVanilla", "0.0.0")] public partial ArgConfig<string> VanillaVersion { get; }
[ConfigItem<string>("VersionModpackVersion", "")] public partial ArgConfig<string> ModpackVersion { get; }
[ConfigItem<string>("VersionModpackSource", "")] public partial ArgConfig<string> ModpackSource { get; }
[ConfigItem<string>("VersionModpackId", "")] public partial ArgConfig<string> ModpackId { get; }
}
}
@@ -0,0 +1,35 @@
using System.Threading;
using System.Threading.Tasks;
namespace PCL.Core.App.Tasks;
/// <summary>
/// 任务状态改变事件
/// <param name="state">当前状态,将影响日志和 UI 显示效果</param>
/// <param name="message">状态消息</param>
/// </summary>
public delegate void TaskStateEvent(TaskState state, string message);
/// <summary>
/// 响应式任务接口<br/>
/// <b>NOTE</b>: 为确保运行时响应式模型的 hash 映射正常工作,若无特殊需求,请勿重写对象相等性实现如
/// <see cref="object.GetHashCode"/> 与 <see cref="object.Equals(object)"/>
/// </summary>
public interface ITask
{
/// <summary>
/// 任务标题
/// </summary>
public string Title { get; }
/// <summary>
/// 运行任务
/// </summary>
/// <param name="cancelToken">取消令牌</param>
public Task ExecuteAsync(CancellationToken cancelToken = default);
/// <summary>
/// 任务状态改变事件
/// </summary>
public event TaskStateEvent StateChanged;
}
@@ -0,0 +1,9 @@
namespace PCL.Core.App.Tasks;
/// <summary>
/// 用于取消实现的接口
/// </summary>
public interface ITaskCancelable
{
public void Cancel();
}
@@ -0,0 +1,9 @@
namespace PCL.Core.App.Tasks;
public delegate void TaskGroupEvent(ITask task);
public interface ITaskGroup : ITask
{
public event TaskGroupEvent AddTask;
public event TaskGroupEvent RemoveTask;
}
@@ -0,0 +1,9 @@
namespace PCL.Core.App.Tasks;
/// <summary>
/// 用于暂停实现的接口
/// </summary>
public interface ITaskPausable
{
public void Pause();
}
@@ -0,0 +1,18 @@
namespace PCL.Core.App.Tasks;
/// <summary>
/// 任务进度改变事件
/// </summary>
/// <param name="progress">0.0 ~ 1.0 之间的浮点数,表示任务进度</param>
public delegate void TaskProgressEvent(double progress);
/// <summary>
/// 可观察进度的任务模型
/// </summary>
public interface ITaskProgressive
{
/// <summary>
/// 任务进度改变事件
/// </summary>
public event TaskProgressEvent ProgressChanged;
}
@@ -0,0 +1,107 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using PCL.Core.Logging;
namespace PCL.Core.App.Tasks;
/// <summary>
/// 任务中心,用于管理任务
/// </summary>
public static class TaskCenter
{
/// <summary>
/// 可观察的任务模型集合
/// </summary>
public static ObservableCollection<TaskModel> Tasks { get; } = [];
private static readonly ConditionalWeakTable<ITask, TaskModel> _ModelMap = [];
private static TaskModel _InitModel(ITask instance)
{
// ReSharper disable SuspiciousTypeConversion.Global
var cancelable = instance as ITaskCancelable;
var pausable = instance as ITaskPausable;
var progressive = instance as ITaskProgressive;
// ReSharper restore SuspiciousTypeConversion.Global
var model = new TaskModel
{
Title = instance.Title,
SupportProgress = progressive is not null,
OnCancel = cancelable is null ? null : (() => cancelable.Cancel()),
OnPause = pausable is null ? null : (() => pausable.Pause()),
};
// state event
instance.StateChanged += (state, message) =>
{
LogWrapper.Trace("TaskCenter", $"{instance.Title}: state changed ({state}): {message}");
model.State = state;
model.StateMessage = message;
};
// progress event
if (progressive is not null)
{
progressive.ProgressChanged += progress =>
{
model.Progress = Math.Clamp(progress, 0.0, 1.0);
};
}
// group events
if (instance is ITaskGroup group)
{
group.AddTask += task =>
{
var taskModel = _InitModel(task);
_ModelMap.Add(task, taskModel);
model.Children.Add(taskModel);
};
group.RemoveTask += task =>
{
if (_ModelMap.TryGetValue(task, out var taskModel)) model.Children.Remove(taskModel);
};
}
return model;
}
/// <summary>
/// 注册响应式任务实例
/// </summary>
/// <param name="instance">任务实例</param>
/// <param name="start">是否立即启动该实例</param>
public static void Register(ITask instance, bool start = true)
{
var model = _InitModel(instance);
Tasks.Add(model);
if (start)
{
_ = Task.Run(async () =>
{
try { await instance.ExecuteAsync(); }
catch (OperationCanceledException) { /* ignoring */ }
catch (Exception ex)
{
LogWrapper.Warn(ex, "TaskCenter", $"{instance.Title}: exception thrown");
model.State = TaskState.Failed;
model.StateMessage = ex.Message;
}
});
}
}
/// <summary>
/// 移除所有已结束的任务
/// </summary>
public static void RemoveFinished()
{
foreach (var model in Tasks.Where(x => x.State > TaskState.Running).ToList())
Tasks.Remove(model);
}
}
@@ -0,0 +1,84 @@
using System;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace PCL.Core.App.Tasks;
/// <summary>
/// 可观察的任务模型<br/>
/// <b>NOTE</b>: 请勿自行修改任何 observable 属性
/// </summary>
public partial class TaskModel : ObservableObject
{
/// <summary>
/// 任务标题
/// </summary>
public required string Title { get; init; }
/// <summary>
/// 任务是否支持进度
/// </summary>
public required bool SupportProgress { get; init; }
/// <summary>
/// 任务当前状态
/// </summary>
[ObservableProperty] private TaskState _state = TaskState.Waiting;
/// <summary>
/// 任务当前状态信息
/// </summary>
[ObservableProperty] private string _stateMessage = string.Empty;
/// <summary>
/// 任务当前进度,<see cref="SupportProgress"/> 为 <see langword="true"/> 时有效
/// </summary>
[ObservableProperty] private double _progress = 0.0;
private static readonly Action _EmptyAction = (static () => {});
/// <summary>
/// 取消任务时触发的事件,值为 <see langword="null"/> 表示不支持取消
/// </summary>
public required Action? OnCancel { private get; init; }
/// <summary>
/// 取消任务命令
/// </summary>
public RelayCommand Cancel
{
get => field ??= new RelayCommand(OnCancel ?? _EmptyAction, () => OnCancel is not null);
} = null!;
/// <summary>
/// 暂停任务时触发的事件,值为 <see langword="null"/> 表示不支持暂停
/// </summary>
public required Action? OnPause { private get; init; }
/// <summary>
/// 暂停任务命令
/// </summary>
public RelayCommand Pause
{
get => field ??= new RelayCommand(OnPause ?? _EmptyAction, () => OnPause is not null);
} = null!;
/// <summary>
/// 任务是否为任务组,即是否存在子任务
/// </summary>
[ObservableProperty] private bool _isGroup;
/// <summary>
/// 子任务模型
/// </summary>
public ObservableCollection<TaskModel> Children { get; } = [];
public TaskModel()
{
Children.CollectionChanged += (sender, _) =>
{
if (sender is ObservableCollection<TaskModel> c) IsGroup = c.Count > 0;
};
}
}
@@ -0,0 +1,10 @@
namespace PCL.Core.App.Tasks;
public enum TaskState
{
Waiting,
Running,
Success,
Canceled,
Failed
}
@@ -0,0 +1,110 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using PCL.Core.App.IoC;
using PCL.Core.App.Localization;
using PCL.Core.UI;
using PCL.Core.Utils;
namespace PCL.Core.App.Tools;
[LifecycleScope("dependency-check", "依赖检查")]
[LifecycleService(LifecycleState.Running)]
public sealed partial class DependencyCheckService
{
[LifecycleStart]
private static async Task _StartAsync()
{
Context.Info(Lang.Text("Tools.Test.Dependency.Checking"));
if (RuntimeInformation.OSArchitecture.Equals(Architecture.Arm64))
await _CheckAndAskAsync("Microsoft.D3DMappingLayers", "OpenGL 兼容包", "9nqpsl29bfff")
.ConfigureAwait(false);
await _CheckAndAskAsync("Microsoft.WebpImageExtension", "WebP 组件包", "9pg2dk419drg")
.ConfigureAwait(false);
}
private static async Task _CheckAndAskAsync(string packageId, string packageName, string storeId)
{
if (!await _CheckPackageAsync(packageId))
{
Context.Info($"检测到依赖缺失 (package-id = {packageId})");
var selection = MsgBoxWrapper.Show(
Lang.Text("Tools.Test.Dependency.MissingMessage", packageName),
buttons: [Lang.Text("Common.Action.Confirm"), Lang.Text("Tools.Test.Dependency.Later")]);
if (selection == 1) _LaunchMsStore(storeId);
}
}
private static void _LaunchMsStore(string id)
{
Context.Info($"正在打开微软应用商店 (id = {id})");
var psi = new ProcessStartInfo()
{
FileName = $"ms-windows-store://pdp?launch=true&productid={id}&mode=full",
UseShellExecute = true
};
using var ps = new Process() { StartInfo = psi };
ps.Start();
}
private static async Task<bool> _CheckPackageAsync(string id)
{
var command = $"Get-AppxPackage -Name *{id}* | ConvertTo-Json";
var psi = new ProcessStartInfo()
{
FileName = "powershell.exe",
Arguments = $"-NoProfile -Command \"{command}\"",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using var ps = new Process() { StartInfo = psi };
ps.Start();
JsonNode? jnode;
try
{
// ConvertTo-Json 结构不一定可靠,不太能 Serialize
jnode = await JsonNode.ParseAsync(ps.StandardOutput.BaseStream, JsonCompat.NodeOptions, JsonCompat.DocumentOptions);
}
catch
{
return false;
}
if (jnode is null) return false;
if (jnode.GetValueKind().Equals(JsonValueKind.Array))
{
var hasPack = false;
foreach (var node in jnode.AsArray())
{
if (node is not null && CheckPackSuit(node, id))
{
hasPack = true;
break;
}
}
return hasPack;
}
else
{
return CheckPackSuit(jnode, id);
}
// 检查当前的 JsonNode 下是否是符合的包
static bool CheckPackSuit(JsonNode jnode, string id)
{
var findedPackName = JsonCompat.ToObject<string>(jnode["Name"]);
return findedPackName is not null && findedPackName.Contains(id);
}
}
}

Some files were not shown because too many files have changed in this diff Show More