From 48add32861504f37cb014fd13be3a3612efacd26 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Thu, 3 Sep 2026 09:55:00 +0200 Subject: [PATCH 01/11] offline play (#362) * offline play * Extract Retro-WFC payload decision policy --- .../Features/Recomp/RecompTests.cs | 82 ++++++++++ .../Recomp/Domain/RecompInstallModels.cs | 33 ++++ .../Features/Recomp/RecompExtensions.cs | 1 + .../Features/Recomp/RecompInstallService.cs | 145 +++++++++++++++--- WheelWizard/Features/Recomp/RecompLauncher.cs | 13 ++ .../Recomp/RecompRetroWfcPayloadPolicy.cs | 61 ++++++++ .../Recomp/RecompRetroWfcPayloadProbe.cs | 89 +++++++++++ .../Recomp/RecompSetupCommandBuilder.cs | 17 +- WheelWizard/Resources/Languages/en.yml | 4 + 9 files changed, 418 insertions(+), 27 deletions(-) create mode 100644 WheelWizard/Features/Recomp/RecompRetroWfcPayloadPolicy.cs create mode 100644 WheelWizard/Features/Recomp/RecompRetroWfcPayloadProbe.cs diff --git a/WheelWizard.Test/Features/Recomp/RecompTests.cs b/WheelWizard.Test/Features/Recomp/RecompTests.cs index 1b4764047..7cfc54540 100644 --- a/WheelWizard.Test/Features/Recomp/RecompTests.cs +++ b/WheelWizard.Test/Features/Recomp/RecompTests.cs @@ -31,6 +31,88 @@ public void SilentInstall_BuildsTheCommandLineTheSetupExecutableExpects() ); } + [Fact] + public void SilentInstall_SkipsThePayloadOnlyWhenAskedTo() + { + var arguments = RecompSetupCommandBuilder.BuildSilentInstallArguments( + new() + { + GameFilePath = @"D:\Games\Mario Kart Wii.rvz", + InstallFolderPath = @"D:\WheelWizard\Recomp\Install", + RetroRewindFolderPath = @"D:\WheelWizard\RetroRewind6", + RetroWfcPayloadMode = RecompRetroWfcPayloadMode.Skip, + } + ); + + Assert.EndsWith("--retro-dir \"D:\\WheelWizard\\RetroRewind6\" --skip-retro-wfc-payload", arguments); + Assert.DoesNotContain("--download-retro-wfc-payload", arguments); + } + + [Fact] + public void RepairProducts_PassesExactlyOnePayloadOption() + { + Assert.Equal( + "--repair-products --install-dir \"D:\\Recomp\" --retro-dir \"D:\\RetroRewind6\" --download-retro-wfc-payload --progress-json", + RecompSetupCommandBuilder.BuildRepairProductsArguments(@"D:\Recomp", @"D:\RetroRewind6") + ); + Assert.Equal( + "--repair-products --install-dir \"D:\\Recomp\" --retro-dir \"D:\\RetroRewind6\" --skip-retro-wfc-payload --progress-json", + RecompSetupCommandBuilder.BuildRepairProductsArguments(@"D:\Recomp", @"D:\RetroRewind6", RecompRetroWfcPayloadMode.Skip) + ); + } + + [Fact] + public void InstallState_ReadsThePayloadModeTheSetupHostWrites() + { + var state = System.Text.Json.JsonSerializer.Deserialize( + """{"SchemaVersion":1,"SetupVersion":"0.3.0","InstallDir":"D:\\Recomp","RetroWfcPayloadMode":"skipped"}""", + new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true } + ); + + Assert.NotNull(state); + Assert.True(state.IsRetroWfcPayloadSkipped); + } + + [Fact] + public void PayloadPolicy_LegacyStateWithoutModeKeepsDownloadingWhenTheServiceIsDown() + { + // Written by a host that predates the mode field: Retro Rewind is installed, so the host owns a + // verified payload copy and must be asked to download, never to skip or to bother the user. + var legacy = new RecompInstallState + { + SchemaVersion = 1, + SetupVersion = "0.2.25", + InstallDir = @"D:\Recomp", + RetroRewindInstalled = true, + }; + + Assert.False(RecompRetroWfcPayloadPolicy.NeedsServiceProbe(legacy, hasRetroRewindSource: true)); + Assert.Equal(RecompRetroWfcPayloadDecision.Download, RecompRetroWfcPayloadPolicy.Decide(legacy, true, serviceReachable: false)); + } + + [Fact] + public void PayloadPolicy_OnlyAFreshRetroRewindBuildAsksTheUser() + { + var downloaded = new RecompInstallState { RetroWfcPayloadMode = "downloaded", RetroRewindInstalled = true }; + var skipped = new RecompInstallState { RetroWfcPayloadMode = "skipped", RetroRewindInstalled = true }; + var baseOnly = new RecompInstallState { RetroWfcPayloadMode = "", RetroRewindInstalled = false }; + + // Nothing to decide without a Retro Rewind source, and never a probe for a payload-bearing install. + Assert.False(RecompRetroWfcPayloadPolicy.NeedsServiceProbe(null, hasRetroRewindSource: false)); + Assert.False(RecompRetroWfcPayloadPolicy.NeedsServiceProbe(downloaded, hasRetroRewindSource: true)); + Assert.Equal(RecompRetroWfcPayloadDecision.Download, RecompRetroWfcPayloadPolicy.Decide(downloaded, true, serviceReachable: false)); + + // A skipped install stays offline while the service is down and upgrades when it is back. + Assert.True(RecompRetroWfcPayloadPolicy.NeedsServiceProbe(skipped, hasRetroRewindSource: true)); + Assert.Equal(RecompRetroWfcPayloadDecision.Skip, RecompRetroWfcPayloadPolicy.Decide(skipped, true, serviceReachable: false)); + Assert.Equal(RecompRetroWfcPayloadDecision.Download, RecompRetroWfcPayloadPolicy.Decide(skipped, true, serviceReachable: true)); + + // A fresh install and a base-only install both need a brand new Retro Rewind build. + Assert.Equal(RecompRetroWfcPayloadDecision.AskUser, RecompRetroWfcPayloadPolicy.Decide(null, true, serviceReachable: false)); + Assert.Equal(RecompRetroWfcPayloadDecision.AskUser, RecompRetroWfcPayloadPolicy.Decide(baseOnly, true, serviceReachable: false)); + Assert.Equal(RecompRetroWfcPayloadDecision.Download, RecompRetroWfcPayloadPolicy.Decide(null, true, serviceReachable: true)); + } + [Fact] public void ProductsLine_IsReadBackAsTheProductStateItReports() { diff --git a/WheelWizard/Features/Recomp/Domain/RecompInstallModels.cs b/WheelWizard/Features/Recomp/Domain/RecompInstallModels.cs index 6ab4c91f8..bd2fb7560 100644 --- a/WheelWizard/Features/Recomp/Domain/RecompInstallModels.cs +++ b/WheelWizard/Features/Recomp/Domain/RecompInstallModels.cs @@ -24,6 +24,33 @@ public class RecompInstallState public int? SchemaVersion { get; set; } public string? SetupVersion { get; set; } public string? InstallDir { get; set; } + + /// Whether the setup host has produced the Retro Rewind product, as opposed to only the base game. + public bool RetroRewindInstalled { get; set; } + + /// + /// How the installed Retro Rewind product was built: downloaded when it embeds a Retro-WFC + /// payload, skipped when it was deliberately built without one, or empty for a base-only install. + /// A state written before this field existed leaves it ; see + /// for how that is read. + /// + public string? RetroWfcPayloadMode { get; set; } + + /// Whether the installed Retro Rewind product was built without a Retro-WFC payload. + public bool IsRetroWfcPayloadSkipped => string.Equals(RetroWfcPayloadMode, "skipped", StringComparison.OrdinalIgnoreCase); +} + +/// +/// The Retro-WFC payload choice handed to the setup executable. The contract requires exactly one whenever a +/// Retro Rewind source directory is passed. +/// +public enum RecompRetroWfcPayloadMode +{ + /// Let the setup host download the shared payload (--download-retro-wfc-payload). + Download, + + /// Build Retro Rewind without the payload (--skip-retro-wfc-payload); online play is unavailable. + Skip, } /// @@ -47,4 +74,10 @@ public sealed record RecompInstallRequest /// an installation that predates portable mode keeps its machine-local layout. /// public bool Portable { get; init; } + + /// + /// The Retro-WFC payload choice for the Retro Rewind product. Only meaningful together with + /// . + /// + public RecompRetroWfcPayloadMode RetroWfcPayloadMode { get; init; } = RecompRetroWfcPayloadMode.Download; } diff --git a/WheelWizard/Features/Recomp/RecompExtensions.cs b/WheelWizard/Features/Recomp/RecompExtensions.cs index f6452de41..23c7002c0 100644 --- a/WheelWizard/Features/Recomp/RecompExtensions.cs +++ b/WheelWizard/Features/Recomp/RecompExtensions.cs @@ -31,6 +31,7 @@ public static IServiceCollection AddRecomp(this IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddTransient(); diff --git a/WheelWizard/Features/Recomp/RecompInstallService.cs b/WheelWizard/Features/Recomp/RecompInstallService.cs index b2522bc9c..111cff493 100644 --- a/WheelWizard/Features/Recomp/RecompInstallService.cs +++ b/WheelWizard/Features/Recomp/RecompInstallService.cs @@ -39,7 +39,17 @@ public interface IRecompInstallService : IDisposable /// Installs a missing/newer setup release, or asks the installed setup host what needs doing and /// repairs only that. An asset-only Retro Rewind change reports current and does no work. /// - Task InstallAsync(IProgress? progress = null, CancellationToken cancellationToken = default); + /// + /// Consulted only when a fresh Retro Rewind build is needed and the Retro-WFC payload service is + /// unreachable. Returning true builds without online play; false or fails the + /// install with an explanation instead. An installation that already embeds a payload never asks: the + /// setup host falls back to its own verified copy. + /// + Task InstallAsync( + IProgress? progress = null, + Func>? confirmOfflineInstall = null, + CancellationToken cancellationToken = default + ); /// /// Verifies product health immediately before launch and repairs only what the check demands. @@ -86,11 +96,15 @@ public sealed class RecompInstallService : IRecompInstallService // installation is probed by pattern rather than by recomputing the backend's own path. private const string OperationLockSearchPattern = ".mkwc-operation-*.lock"; + private const string RetroWfcUnavailableMessage = + "The Retro WFC servers are not responding, so WiiCompiled cannot set up online play right now. Try again later, or install without online play."; + private static readonly JsonSerializerOptions InstallStateJsonOptions = new() { PropertyNameCaseInsensitive = true }; private readonly IRecompEnvironment environment; private readonly IRecompProcessRunner processRunner; private readonly IRecompSetupDownloader downloader; + private readonly IRecompRetroWfcPayloadProbe payloadProbe; private readonly IGitHubSingletonService gitHubService; private readonly IFileSystem fileSystem; private readonly ILogger logger; @@ -105,6 +119,7 @@ public RecompInstallService( IRecompEnvironment environment, IRecompProcessRunner processRunner, IRecompSetupDownloader downloader, + IRecompRetroWfcPayloadProbe payloadProbe, IGitHubSingletonService gitHubService, IFileSystem fileSystem, ILogger logger @@ -113,6 +128,7 @@ ILogger logger this.environment = environment; this.processRunner = processRunner; this.downloader = downloader; + this.payloadProbe = payloadProbe; this.gitHubService = gitHubService; this.fileSystem = fileSystem; this.logger = logger; @@ -149,13 +165,60 @@ public async Task GetCurrentStatusAsync(CancellationToken can if (installationBusy) logger.LogInformation("The WiiCompiled product check could not run because the installation is busy"); - return RecompStatusResolver.Resolve( + var status = RecompStatusResolver.Resolve( IsGameFileConfigured(), hasInstalledHost ? installedVersion : null, latestRelease?.TagName, products?.IsSuccess == true ? products.Value : null, installationBusy ); + + // A build that skipped the payload is healthy as far as the host is concerned, but it cannot + // play online. Once the payload service is back, that is an update worth offering. + if ( + status is (WheelWizardStatus.Ready or WheelWizardStatus.NoServerButInstalled) + && await RetroWfcUpgradeAvailableAsync(state, cancellationToken) + ) + return WheelWizardStatus.OutOfDate; + + return status; + } + + /// + /// Whether the installed Retro Rewind product was built without a Retro-WFC payload while the payload + /// service is reachable again. Only a skipped installation ever probes, so a normal one costs nothing. + /// + private async Task RetroWfcUpgradeAvailableAsync(RecompInstallState? state, CancellationToken cancellationToken) => + state is { IsRetroWfcPayloadSkipped: true } && await payloadProbe.IsReachableAsync(cancellationToken); + + /// + /// Decides which payload option the next setup operation receives. The rules live in + /// ; this only supplies the probe and the user's answer. + /// + private async Task> ResolveRetroWfcPayloadModeAsync( + RecompInstallState? state, + Func>? confirmOfflineInstall, + CancellationToken cancellationToken + ) + { + var hasRetroRewindSource = !string.IsNullOrWhiteSpace(environment.RetroRewindFolderPath); + var serviceReachable = + !RecompRetroWfcPayloadPolicy.NeedsServiceProbe(state, hasRetroRewindSource) + || await payloadProbe.IsReachableAsync(cancellationToken); + + switch (RecompRetroWfcPayloadPolicy.Decide(state, hasRetroRewindSource, serviceReachable)) + { + case RecompRetroWfcPayloadDecision.Download: + return Ok(RecompRetroWfcPayloadMode.Download); + case RecompRetroWfcPayloadDecision.Skip: + return Ok(RecompRetroWfcPayloadMode.Skip); + default: + if (confirmOfflineInstall is null || !await confirmOfflineInstall()) + return Fail(RetroWfcUnavailableMessage); + + logger.LogInformation("The Retro-WFC payload service is unreachable; installing WiiCompiled without online play"); + return Ok(RecompRetroWfcPayloadMode.Skip); + } } /// @@ -265,6 +328,7 @@ private async Task> CheckProductsCoreAsync( public async Task InstallAsync( IProgress? progress = null, + Func>? confirmOfflineInstall = null, CancellationToken cancellationToken = default ) { @@ -273,7 +337,7 @@ public async Task InstallAsync( try { - return await InstallCoreAsync(progress, cancellationToken); + return await InstallCoreAsync(progress, confirmOfflineInstall, cancellationToken); } finally { @@ -281,7 +345,11 @@ public async Task InstallAsync( } } - private async Task InstallCoreAsync(IProgress? progress, CancellationToken cancellationToken) + private async Task InstallCoreAsync( + IProgress? progress, + Func>? confirmOfflineInstall, + CancellationToken cancellationToken + ) { // No platform guard here on purpose: AddRecomp() is the single gate, so this service only ever // exists on Windows in the first place. @@ -289,28 +357,40 @@ private async Task InstallCoreAsync(IProgress ReconcileForLaunchAsync( @@ -346,7 +426,19 @@ CancellationToken cancellationToken if (!await SetupMatchesVersionAsync(environment.InstalledSetupFilePath, state.SetupVersion, cancellationToken)) return Fail("The installed WiiCompiled host does not match its current install state."); - var repairResult = await RepairWhatTheCheckDemandsCoreAsync(progress, reportCompletion: false, cancellationToken); + // A launch never asks about offline play and never forces the payload upgrade: the user pressed + // Play, not Update. A repair the check demands anyway still gains the payload when it is reachable. + var payloadModeResult = await ResolveRetroWfcPayloadModeAsync(state, confirmOfflineInstall: null, cancellationToken); + if (payloadModeResult.IsFailure) + return payloadModeResult.Error; + + var repairResult = await RepairWhatTheCheckDemandsCoreAsync( + progress, + payloadModeResult.Value, + forceRetroRebuild: false, + reportCompletion: false, + cancellationToken + ); if (repairResult.IsFailure) return repairResult; @@ -582,6 +674,7 @@ private bool DeleteInvalidSetup(string setupFilePath) private async Task RunSilentInstallAsync( string setupFilePath, + RecompRetroWfcPayloadMode payloadMode, IProgress? progress, CancellationToken cancellationToken ) @@ -592,6 +685,7 @@ CancellationToken cancellationToken InstallFolderPath = environment.InstallFolderPath, RetroRewindFolderPath = environment.RetroRewindFolderPath, Portable = environment.IsPortableInstall, + RetroWfcPayloadMode = payloadMode, }; var arguments = RecompSetupCommandBuilder.BuildSilentInstallArguments(request); @@ -621,11 +715,20 @@ CancellationToken cancellationToken /// private async Task RepairWhatTheCheckDemandsAsync( IProgress? progress, + RecompRetroWfcPayloadMode payloadMode, + bool forceRetroRebuild, CancellationToken cancellationToken - ) => await RepairWhatTheCheckDemandsCoreAsync(progress, reportCompletion: true, cancellationToken); + ) => await RepairWhatTheCheckDemandsCoreAsync(progress, payloadMode, forceRetroRebuild, reportCompletion: true, cancellationToken); + /// + /// Runs the repair even when the check reports every product current. The host decides for itself + /// what that repair rebuilds; this exists so a payload choice that changed, which the check does + /// not see, still reaches it. + /// private async Task RepairWhatTheCheckDemandsCoreAsync( IProgress? progress, + RecompRetroWfcPayloadMode payloadMode, + bool forceRetroRebuild, bool reportCompletion, CancellationToken cancellationToken ) @@ -634,7 +737,7 @@ CancellationToken cancellationToken if (checkResult.IsFailure) return checkResult.Error; - if (!NeedsRepair(checkResult.Value)) + if (!NeedsRepair(checkResult.Value) && !forceRetroRebuild) { if (reportCompletion) Report(progress, t("progress.recomp_finished"), 100); @@ -642,13 +745,14 @@ CancellationToken cancellationToken } logger.LogInformation( - "Repairing WiiCompiled products (base: {BaseStatus}, retro: {RetroStatus}, compile required: {CompileRequired})", + "Repairing WiiCompiled products (base: {BaseStatus}, retro: {RetroStatus}, compile required: {CompileRequired}, payload: {PayloadMode})", checkResult.Value.Base.State, checkResult.Value.RetroRewind.State, - checkResult.Value.Base.RequiresCompile || checkResult.Value.RetroRewind.RequiresCompile + checkResult.Value.Base.RequiresCompile || checkResult.Value.RetroRewind.RequiresCompile, + payloadMode ); - var repairResult = await RunTargetedRepairAsync(progress, cancellationToken); + var repairResult = await RunTargetedRepairAsync(progress, payloadMode, cancellationToken); if (repairResult.IsFailure) return repairResult; @@ -667,6 +771,7 @@ CancellationToken cancellationToken private async Task RunTargetedRepairAsync( IProgress? progress, + RecompRetroWfcPayloadMode payloadMode, CancellationToken cancellationToken ) { @@ -676,7 +781,11 @@ CancellationToken cancellationToken if (string.IsNullOrWhiteSpace(retroRewindFolderPath)) return Fail("Retro Rewind must be installed before WiiCompiled can repair or launch."); - var arguments = RecompSetupCommandBuilder.BuildRepairProductsArguments(environment.InstallFolderPath, retroRewindFolderPath); + var arguments = RecompSetupCommandBuilder.BuildRepairProductsArguments( + environment.InstallFolderPath, + retroRewindFolderPath, + payloadMode + ); Report(progress, t("progress.recomp_running_setup"), SetupPercentFloor); var resultHolder = new EventHolder(); @@ -784,12 +893,6 @@ private OperationResult FinishSetupRun( return RecompReleaseResolver.FindLatest(releasesResult.Value); } - private string? ReadInstalledVersion() - { - var state = ReadInstalledState(); - return string.IsNullOrWhiteSpace(state?.SetupVersion) ? null : state.SetupVersion; - } - private RecompInstallState? ReadInstalledState() { try diff --git a/WheelWizard/Features/Recomp/RecompLauncher.cs b/WheelWizard/Features/Recomp/RecompLauncher.cs index a44796cb5..45b1cf7b5 100644 --- a/WheelWizard/Features/Recomp/RecompLauncher.cs +++ b/WheelWizard/Features/Recomp/RecompLauncher.cs @@ -249,6 +249,7 @@ private async Task RunSetupAsync(string goal) // handed an uncancellable token. Whatever the backend reports is the truth. return await installService.InstallAsync( progress, + ConfirmOfflineInstallAsync, retroRewindCommitted ? CancellationToken.None : cancellationTokenSource.Token ); } @@ -262,6 +263,18 @@ private async Task RunSetupAsync(string goal) } } + /// + /// Asked by the install service only when a Retro Rewind build is needed and the Retro-WFC payload + /// service is down. Offline-only is a real choice, not a silent downgrade: the game plays, online + /// does not, and the next update after the service returns rebuilds with online play automatically. + /// + private static Task ConfirmOfflineInstallAsync() => + new YesNoWindow() + .SetButtonText(t("action.recomp_install_offline"), t("action.cancel")) + .SetMainText(t("question.recomp_retro_wfc_unavailable.title")) + .SetExtraText(t("question.recomp_retro_wfc_unavailable.extra")) + .AwaitAnswer(); + private async Task> EnsureRetroRewindCurrentAsync( ProgressWindow progressWindow, CancellationToken cancellationToken diff --git a/WheelWizard/Features/Recomp/RecompRetroWfcPayloadPolicy.cs b/WheelWizard/Features/Recomp/RecompRetroWfcPayloadPolicy.cs new file mode 100644 index 000000000..0a0d01265 --- /dev/null +++ b/WheelWizard/Features/Recomp/RecompRetroWfcPayloadPolicy.cs @@ -0,0 +1,61 @@ +using WheelWizard.Recomp.Domain; + +namespace WheelWizard.Recomp; + +/// +/// What the install service should do about the Retro-WFC payload for the next setup operation. +/// +public enum RecompRetroWfcPayloadDecision +{ + /// Pass --download-retro-wfc-payload. + Download, + + /// Pass --skip-retro-wfc-payload without asking: the installation is already offline-only. + Skip, + + /// A fresh Retro Rewind build cannot get its payload; only the user can choose offline-only. + AskUser, +} + +/// +/// The pure decision behind the payload option, kept free of I/O so the cases can be tested directly. +/// An installation that already embeds a payload always asks the host to download, because the host +/// falls back to its own verified copy when the service is down. A skipped installation upgrades when +/// the service is back and otherwise stays as it is. Only a Retro Rewind build with nothing to fall +/// back to is the user's call. +/// +public static class RecompRetroWfcPayloadPolicy +{ + /// + /// Whether the decision depends on the payload service at all. When it does not, no probe should run: + /// a normal installation must never wait on a dead endpoint just to be told what it already knows. + /// + public static bool NeedsServiceProbe(RecompInstallState? state, bool hasRetroRewindSource) => + hasRetroRewindSource && !HasVerifiedPayload(state); + + /// + /// Decides the payload option. is only consulted when + /// says so; pass any value otherwise. + /// + public static RecompRetroWfcPayloadDecision Decide(RecompInstallState? state, bool hasRetroRewindSource, bool serviceReachable) + { + if (!NeedsServiceProbe(state, hasRetroRewindSource) || serviceReachable) + return RecompRetroWfcPayloadDecision.Download; + + return state is { IsRetroWfcPayloadSkipped: true } ? RecompRetroWfcPayloadDecision.Skip : RecompRetroWfcPayloadDecision.AskUser; + } + + /// + /// Whether the installed Retro Rewind product carries a payload the host can fall back to. A state + /// that predates the mode field but reports Retro Rewind installed counts: every host so far has + /// only ever built Retro Rewind with a downloaded payload, so absent means downloaded, never skipped. + /// + private static bool HasVerifiedPayload(RecompInstallState? state) + { + if (state is null) + return false; + if (string.Equals(state.RetroWfcPayloadMode, "downloaded", StringComparison.OrdinalIgnoreCase)) + return true; + return string.IsNullOrWhiteSpace(state.RetroWfcPayloadMode) && state.RetroRewindInstalled; + } +} diff --git a/WheelWizard/Features/Recomp/RecompRetroWfcPayloadProbe.cs b/WheelWizard/Features/Recomp/RecompRetroWfcPayloadProbe.cs new file mode 100644 index 000000000..90e60c4f6 --- /dev/null +++ b/WheelWizard/Features/Recomp/RecompRetroWfcPayloadProbe.cs @@ -0,0 +1,89 @@ +using Microsoft.Extensions.Logging; + +namespace WheelWizard.Recomp; + +/// +/// Answers whether the shared Retro-WFC payload can currently be downloaded. The setup host is the only +/// thing that ever downloads and verifies the payload; this probe exists so WheelWizard can decide, before +/// starting a long install, whether to ask the user for an offline-only build instead of failing later. +/// +public interface IRecompRetroWfcPayloadProbe +{ + /// + /// True when the payload endpoint answered with a success status. Never throws: any failure, including + /// a timeout, simply reads as unreachable. + /// + Task IsReachableAsync(CancellationToken cancellationToken = default); +} + +/// +public sealed class RecompRetroWfcPayloadProbe(IHttpClientFactory httpClientFactory, ILogger logger) + : IRecompRetroWfcPayloadProbe +{ + /// + /// The fixed payload endpoint the setup host downloads from. It must stay identical to the value pinned + /// in the recomp's RetroWfcPayload.CurrentRetroWfcPayloadUri, since probing anything else says + /// nothing about whether the host's own download will succeed. Plain HTTP is what the service offers + /// and what the host pins; that is safe here because this probe decides nothing about trust. The host + /// verifies the payload's RSA signature itself, so a forged answer can at worst make the host attempt + /// a download that its own verification then rejects. + /// + public const string PayloadUri = "http://nas.play.rwfc.net/payload?g=RMCPD00"; + + // A dead endpoint times out rather than refusing, so a short cap is what keeps the home page and the + // install flow from stalling on every status refresh while the service is down. + private static readonly TimeSpan ProbeTimeout = TimeSpan.FromSeconds(6); + + // A status refresh and the install that follows it should not each pay for their own probe. + private static readonly TimeSpan ResultLifetime = TimeSpan.FromSeconds(60); + + private readonly object _cacheLock = new(); + private DateTimeOffset _cachedAtUtc = DateTimeOffset.MinValue; + private bool _cachedResult; + + public async Task IsReachableAsync(CancellationToken cancellationToken = default) + { + lock (_cacheLock) + { + if (DateTimeOffset.UtcNow - _cachedAtUtc < ResultLifetime) + return _cachedResult; + } + + var reachable = await ProbeAsync(cancellationToken); + + lock (_cacheLock) + { + _cachedResult = reachable; + _cachedAtUtc = DateTimeOffset.UtcNow; + } + + return reachable; + } + + private async Task ProbeAsync(CancellationToken cancellationToken) + { + try + { + using var timeoutSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + timeoutSource.CancelAfter(ProbeTimeout); + + // Headers only: the payload is a multi-megabyte binary and its bytes are the host's business. + var client = httpClientFactory.CreateClient(RecompSetupDownloader.HttpClientName); + using var response = await client.GetAsync(PayloadUri, HttpCompletionOption.ResponseHeadersRead, timeoutSource.Token); + if (response.IsSuccessStatusCode) + return true; + + logger.LogWarning("The Retro-WFC payload endpoint answered {StatusCode}", (int)response.StatusCode); + return false; + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + } + catch (Exception exception) + { + logger.LogWarning("The Retro-WFC payload endpoint is unreachable: {Message}", exception.Message); + return false; + } + } +} diff --git a/WheelWizard/Features/Recomp/RecompSetupCommandBuilder.cs b/WheelWizard/Features/Recomp/RecompSetupCommandBuilder.cs index 34499c6fd..c85238188 100644 --- a/WheelWizard/Features/Recomp/RecompSetupCommandBuilder.cs +++ b/WheelWizard/Features/Recomp/RecompSetupCommandBuilder.cs @@ -45,7 +45,7 @@ public static string BuildSilentInstallArguments(RecompInstallRequest request) { arguments.Add("--retro-dir"); arguments.Add(Quote(request.RetroRewindFolderPath)); - arguments.Add(RetroWfcPayloadArgument); + arguments.Add(RetroWfcPayloadArgument(request.RetroWfcPayloadMode)); } return string.Join(' ', arguments); @@ -56,7 +56,11 @@ public static string BuildSilentInstallArguments(RecompInstallRequest request) /// toolkit the installation already owns instead of an embedded payload. Every repair receives the /// Retro Rewind source and exactly one Retro-WFC payload option, as the contract requires. /// - public static string BuildRepairProductsArguments(string installFolderPath, string retroRewindFolderPath) + public static string BuildRepairProductsArguments( + string installFolderPath, + string retroRewindFolderPath, + RecompRetroWfcPayloadMode retroWfcPayloadMode = RecompRetroWfcPayloadMode.Download + ) { if (string.IsNullOrWhiteSpace(installFolderPath)) throw new ArgumentException("An install directory is required.", nameof(installFolderPath)); @@ -70,7 +74,7 @@ public static string BuildRepairProductsArguments(string installFolderPath, stri Quote(installFolderPath), "--retro-dir", Quote(retroRewindFolderPath), - RetroWfcPayloadArgument, + RetroWfcPayloadArgument(retroWfcPayloadMode), "--progress-json" ); } @@ -106,9 +110,10 @@ public static string BuildCheckProductsArguments(string installFolderPath, strin /// public static string BuildVersionArguments() => "--version"; - // The contract requires exactly one payload option (--download-retro-wfc-payload or - // --skip-retro-wfc-payload) whenever --retro-dir is passed; WheelWizard always downloads. - private const string RetroWfcPayloadArgument = "--download-retro-wfc-payload"; + // The contract requires exactly one payload option whenever --retro-dir is passed. WheelWizard + // downloads unless the payload service is unreachable and the user chose an offline-only build. + private static string RetroWfcPayloadArgument(RecompRetroWfcPayloadMode mode) => + mode == RecompRetroWfcPayloadMode.Skip ? "--skip-retro-wfc-payload" : "--download-retro-wfc-payload"; // The recomp is Windows only, so quoting is always Windows quoting (never the POSIX form EnvHelper would pick). private static string Quote(string path) diff --git a/WheelWizard/Resources/Languages/en.yml b/WheelWizard/Resources/Languages/en.yml index d527a45eb..67e678861 100644 --- a/WheelWizard/Resources/Languages/en.yml +++ b/WheelWizard/Resources/Languages/en.yml @@ -141,6 +141,7 @@ en: view_room: "View Room" recomp_nand_copy: "Make a copy" recomp_nand_share: "Use directly" + recomp_install_offline: "Install offline-only" link: discord: "Talk to us!" github: "Source code" @@ -270,6 +271,9 @@ en: recomp_use_dolphin_nand: title: "Use your Dolphin save data?" extra: "WiiCompiled can play with the saves, Miis and friend codes you already use in Dolphin. If not, it starts fresh with its own Wii data." + recomp_retro_wfc_unavailable: + title: "Retro WFC is unreachable" + extra: "WiiCompiled needs the Retro WFC servers to set up online play, and they are not responding right now. You can install without online play now; WiiCompiled will add it automatically the next time you update once the servers are back." recomp_nand_mode: title: "Copy your Dolphin data, or use it directly?" extra: "Dolphin recommends against other programs using its Wii data (NAND) directly, since two programs writing to it at once can corrupt it. A copy keeps WiiCompiled completely separate, but progress made in one will not appear in the other." From 8faf31e4bdab7e0dd3321a6931b2154f4200899c Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:40:19 +0200 Subject: [PATCH 02/11] Bump version to 2.5.3 --- WheelWizard/WheelWizard.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WheelWizard/WheelWizard.csproj b/WheelWizard/WheelWizard.csproj index b55607474..213af112c 100644 --- a/WheelWizard/WheelWizard.csproj +++ b/WheelWizard/WheelWizard.csproj @@ -12,7 +12,7 @@ $(NoWarn);AVLN3001 - 2.5.2 + 2.5.3 This program will manage RetroRewind and mods :) GNU v3.0 https://github.com/patchzyy/WheelWizard From 11c8729871a68ba75448b9c58d550490aa0b5de8 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:42:55 +0200 Subject: [PATCH 03/11] CI: publish the release even when a macOS build fails The Windows/Linux build is the required one. A failed macOS signing or notarization no longer blocks the release; the DMGs are attached only when their jobs produced them, and the in-app updater already picks the newest release that carries an asset for the user's platform. --- .github/workflows/release.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b05227d5a..7a5fd72ed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -321,6 +321,12 @@ jobs: create-release: name: Create GitHub Release needs: [release, macos-arm64, macos-x64] + # The Windows/Linux build is the one that must succeed. A failed macOS job (signing, + # notarization) must not hold the other platforms hostage: the release goes out without the + # DMGs, and the in-app updater already picks the newest release that has an asset for the + # user's platform, so macOS users simply stay on their current version until a later release + # carries DMGs again. + if: ${{ !cancelled() && needs.release.result == 'success' }} runs-on: ubuntu-latest @@ -332,14 +338,19 @@ jobs: run: | mkdir -p final-artifacts - # Linux & Windows + # Linux & Windows: required. cp artifacts-linux-windows/WheelWizardWindows.exe final-artifacts/ cp artifacts-linux-windows/WheelWizard_Linux final-artifacts/ cp artifacts-linux-windows/WheelWizard_arm64_Linux final-artifacts/ - # macOS DMGs - cp artifacts-macos-arm64/WheelWizard-macOSarm64.dmg final-artifacts/ - cp artifacts-macos-x64/WheelWizard-macOSintel.dmg final-artifacts/ + # macOS DMGs: attached when their job produced them, skipped with a warning otherwise. + for dmg in artifacts-macos-arm64/WheelWizard-macOSarm64.dmg artifacts-macos-x64/WheelWizard-macOSintel.dmg; do + if [ -f "$dmg" ]; then + cp "$dmg" final-artifacts/ + else + echo "::warning::$(basename "$dmg") was not produced; publishing this release without it." + fi + done chmod +x final-artifacts/WheelWizard_Linux final-artifacts/WheelWizard_arm64_Linux ls -la final-artifacts/ From b7bc6bf9aad50a04c78c24606e3a6332de7ba81e Mon Sep 17 00:00:00 2001 From: matellush <202132988+matellush@users.noreply.github.com> Date: Sat, 5 Sep 2026 16:47:34 +0200 Subject: [PATCH 04/11] fix(flatpak): Add version `2.5.3` to metainfo `.xml` (#363) --- Flatpak/io.github.TeamWheelWizard.WheelWizard.metainfo.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/Flatpak/io.github.TeamWheelWizard.WheelWizard.metainfo.xml b/Flatpak/io.github.TeamWheelWizard.WheelWizard.metainfo.xml index 33f4531e0..1d35ee678 100644 --- a/Flatpak/io.github.TeamWheelWizard.WheelWizard.metainfo.xml +++ b/Flatpak/io.github.TeamWheelWizard.WheelWizard.metainfo.xml @@ -55,6 +55,7 @@ + From 7a149cb4a51d189e89ddbb51e96245be766e23c7 Mon Sep 17 00:00:00 2001 From: WantToBeeMe | Dirk Date: Sat, 5 Sep 2026 21:54:37 +0200 Subject: [PATCH 05/11] feat: redesign the settings page (#375) * remove the close button from the install thing, less options * re-arange the dolphin settings * update translations * fix translations * fix translation import and export script * improve settings further * better other page * rework path settings of hte wheel wizard page * layout change * small details * Hide development features button in teh development tools popup * support hte linux dolhpin executable setting in the new settings desing * fix comments --------- Co-authored-by: WantToBeeMe <93130991+WantToBeeMe@users.noreply.github.com> --- .gitignore | 1 + .../LocalizationLanguageCatalog.cs | 1 + .../Recomp/RecompDolphinDataService.cs | 18 +- .../Features/Recomp/RecompInstallService.cs | 27 + WheelWizard/Features/Recomp/RecompLauncher.cs | 41 +- .../Features/Settings/ISettingsServices.cs | 1 - .../Features/Settings/SettingsManager.cs | 11 +- WheelWizard/Helpers/HttpClientHelper.cs | 5 +- WheelWizard/Logging.cs | 6 +- WheelWizard/Resources/Languages/cs.yml | 16 +- WheelWizard/Resources/Languages/de.yml | 40 +- WheelWizard/Resources/Languages/en.yml | 85 ++-- WheelWizard/Resources/Languages/es.yml | 25 +- WheelWizard/Resources/Languages/fi.yml | 89 +++- WheelWizard/Resources/Languages/fr.yml | 96 ++-- WheelWizard/Resources/Languages/it.yml | 20 +- WheelWizard/Resources/Languages/ja.yml | 22 +- WheelWizard/Resources/Languages/ko.yml | 28 +- WheelWizard/Resources/Languages/nl.yml | 20 +- WheelWizard/Resources/Languages/pl.yml | 477 ++++++++++++++++++ .../Resources/Languages/port_script.py | 39 +- WheelWizard/Resources/Languages/pt.yml | 107 +++- WheelWizard/Resources/Languages/ru.yml | 18 +- WheelWizard/Resources/Languages/tr.yml | 22 +- WheelWizard/Shared/DevelopmentMode.cs | 14 + .../MessageTranslationHelper.cs | 5 + WheelWizard/Views/Components/Button.axaml | 9 + WheelWizard/Views/Components/Button.axaml.cs | 31 +- .../Views/Components/FormFieldLabel.axaml | 8 +- WheelWizard/Views/Layout.axaml.cs | 14 +- .../Views/Pages/Settings/AppInfo.axaml | 25 +- .../Views/Pages/Settings/AppInfo.axaml.cs | 6 +- .../Views/Pages/Settings/OtherSettings.axaml | 182 +++++-- .../Pages/Settings/OtherSettings.axaml.cs | 18 + .../Views/Pages/Settings/RecompSettings.axaml | 265 ++++++++-- .../Pages/Settings/RecompSettings.axaml.cs | 139 ++++- .../Views/Pages/Settings/VideoSettings.axaml | 271 ++++++---- .../Pages/Settings/VideoSettings.axaml.cs | 45 +- .../Views/Pages/Settings/WhWzSettings.axaml | 441 +++++++++------- .../Pages/Settings/WhWzSettings.axaml.cs | 304 +++++------ WheelWizard/Views/Pages/SettingsPage.axaml.cs | 12 +- WheelWizard/Views/Popups/DevToolWindow.axaml | 5 +- .../Views/Popups/DevToolWindow.axaml.cs | 9 + .../Generic/MiiRenderingSetupPopup.axaml | 9 +- .../Generic/MiiRenderingSetupPopup.axaml.cs | 8 - .../Views/Popups/Generic/ProgressWindow.axaml | 16 +- .../Popups/Generic/ProgressWindow.axaml.cs | 80 ++- .../Popups/Generic/TextInputWindow.axaml | 15 +- .../Views/Popups/Generic/YesNoWindow.axaml | 14 +- .../Views/Styles/Resources/Constants.axaml | 2 +- .../Views/Styles/Styles/DropdownStyles.axaml | 12 +- .../Views/Styles/Styles/MiscStyles.axaml | 22 + 52 files changed, 2227 insertions(+), 969 deletions(-) create mode 100644 WheelWizard/Resources/Languages/pl.yml create mode 100644 WheelWizard/Shared/DevelopmentMode.cs diff --git a/.gitignore b/.gitignore index 635b27bb3..1400b9e3f 100644 --- a/.gitignore +++ b/.gitignore @@ -489,3 +489,4 @@ $RECYCLE.BIN/ # Vim temporary swap files *.swp AGENTS.md +/.dotnet diff --git a/WheelWizard/Features/Localization/LocalizationLanguageCatalog.cs b/WheelWizard/Features/Localization/LocalizationLanguageCatalog.cs index 9b70b93d0..a7e5b3f7d 100644 --- a/WheelWizard/Features/Localization/LocalizationLanguageCatalog.cs +++ b/WheelWizard/Features/Localization/LocalizationLanguageCatalog.cs @@ -15,6 +15,7 @@ public static class LocalizationLanguageCatalog new("ja", "value.language.japanese"), new("es", "value.language.spanish"), new("it", "value.language.italian"), + new("pl", "value.language.polish"), new("pt", "value.language.portuguese"), new("ru", "value.language.russian"), new("ko", "value.language.korean"), diff --git a/WheelWizard/Features/Recomp/RecompDolphinDataService.cs b/WheelWizard/Features/Recomp/RecompDolphinDataService.cs index 9d8264e2f..3395d5c0c 100644 --- a/WheelWizard/Features/Recomp/RecompDolphinDataService.cs +++ b/WheelWizard/Features/Recomp/RecompDolphinDataService.cs @@ -6,9 +6,9 @@ namespace WheelWizard.Recomp; public interface IRecompDolphinDataService { - bool IsEnabled { get; } + bool IsSharingEnabled { get; } - /// Whether the recomp uses a copy of the Dolphin NAND instead of sharing it in place. + /// Whether private mode uses a copy originally imported from Dolphin. bool CopyEnabled { get; } string? LinkedUserFolderPath { get; } @@ -20,7 +20,7 @@ public interface IRecompDolphinDataService string? FindCandidateUserFolder(); OperationResult Link(string userFolderPath); - void SetEnabled(bool enabled); + void SetSharingEnabled(bool enabled); void SetCopyEnabled(bool enabled); OperationResult CopyNandForRecomp(); @@ -31,7 +31,7 @@ public interface IRecompDolphinDataService public sealed class RecompDolphinDataService(ISettingsManager settings, IRecompSettingManager recompSettings, IFileSystem fileSystem) : IRecompDolphinDataService { - public bool IsEnabled => settings.Get(settings.RECOMP_USE_DOLPHIN_DATA); + public bool IsSharingEnabled => settings.Get(settings.RECOMP_USE_DOLPHIN_DATA); public bool CopyEnabled => settings.Get(settings.RECOMP_COPY_DOLPHIN_NAND); @@ -41,15 +41,17 @@ public string? NandFolderPath { get { - if (!IsEnabled) - return null; + // Sharing always means Dolphin's live NAND, even when a private clone is kept for the + // next time sharing is disabled. + if (IsSharingEnabled) + return SourceNandFolderPath; // A missing copy never falls back to the Dolphin NAND: the user chose the copy exactly // so that Dolphin's own data is left alone, and a private NAND is the safe default. if (CopyEnabled) return ValidateNandFolder(PathManager.RecompNandCopyFolderPath); - return SourceNandFolderPath; + return null; } } @@ -84,7 +86,7 @@ public OperationResult Link(string userFolderPath) return Ok(); } - public void SetEnabled(bool enabled) => settings.Set(settings.RECOMP_USE_DOLPHIN_DATA, enabled); + public void SetSharingEnabled(bool enabled) => settings.Set(settings.RECOMP_USE_DOLPHIN_DATA, enabled); public void SetCopyEnabled(bool enabled) => settings.Set(settings.RECOMP_COPY_DOLPHIN_NAND, enabled); diff --git a/WheelWizard/Features/Recomp/RecompInstallService.cs b/WheelWizard/Features/Recomp/RecompInstallService.cs index 111cff493..fc9cb8942 100644 --- a/WheelWizard/Features/Recomp/RecompInstallService.cs +++ b/WheelWizard/Features/Recomp/RecompInstallService.cs @@ -22,6 +22,12 @@ public interface IRecompInstallService : IDisposable /// bool IsInstalled { get; } + /// + /// Runs the installed WiiCompiled-Setup.exe --version host and returns its semantic version. + /// WiiCompiled is Windows-only, so other platforms return . + /// + Task GetInstalledVersionAsync(CancellationToken cancellationToken = default); + /// /// Reads install-state.json, compares it against the latest GitHub release, and asks /// --check-products whether the installed executables are still fresh. @@ -140,6 +146,27 @@ ILogger logger public bool IsInstalled => fileSystem.File.Exists(environment.InstalledSetupFilePath); + public async Task GetInstalledVersionAsync(CancellationToken cancellationToken = default) + { + if (!OperatingSystem.IsWindows() || !IsInstalled) + return null; + + string? versionText = null; + var runResult = await processRunner.RunAsync( + environment.InstalledSetupFilePath, + RecompSetupCommandBuilder.BuildVersionArguments(), + workingDirectory: null, + line => + { + if (RecompVersion.TryParse(line, out var version)) + versionText = version.ToString(); + }, + cancellationToken + ); + + return runResult.IsSuccess && runResult.Value == 0 ? versionText : null; + } + public async Task GetCurrentStatusAsync(CancellationToken cancellationToken = default) { var state = ReadInstalledState(); diff --git a/WheelWizard/Features/Recomp/RecompLauncher.cs b/WheelWizard/Features/Recomp/RecompLauncher.cs index 45b1cf7b5..130f004a3 100644 --- a/WheelWizard/Features/Recomp/RecompLauncher.cs +++ b/WheelWizard/Features/Recomp/RecompLauncher.cs @@ -4,6 +4,7 @@ using WheelWizard.Recomp.Domain; using WheelWizard.Services; using WheelWizard.Services.Launcher; +using WheelWizard.Shared.MessageTranslations; using WheelWizard.Views.Popups.Generic; namespace WheelWizard.Recomp; @@ -57,10 +58,12 @@ public async Task Launch() progressWindow.Show(); var reconciliation = await installService.ReconcileForLaunchAsync(progress, cancellationTokenSource.Token); if (reconciliation.IsFailure) - return reconciliation; + return IsCancellationRequested(progressWindow, cancellationTokenSource) + ? CancellationWarning("WiiCompiled launch preparation was cancelled.") + : reconciliation; - if (progressWindow.WasCancellationRequested || cancellationTokenSource.IsCancellationRequested) - return Fail("WiiCompiled launch preparation was cancelled."); + if (IsCancellationRequested(progressWindow, cancellationTokenSource)) + return CancellationWarning("WiiCompiled launch preparation was cancelled."); // Reconciliation is the only cancellable progress phase. The launch call is // awaited through game exit. @@ -70,7 +73,7 @@ public async Task Launch() } catch (OperationCanceledException) { - return Fail("WiiCompiled launch preparation was cancelled."); + return CancellationWarning("WiiCompiled launch preparation was cancelled."); } finally { @@ -160,7 +163,7 @@ private async Task AskForDolphinNandChoiceAsync() if (sourceNand is null) { // There is no Dolphin data to offer, so the recomp simply starts with its own. - dolphinData.SetEnabled(false); + dolphinData.SetSharingEnabled(false); dolphinData.SetCopyEnabled(false); return Ok(); } @@ -171,12 +174,11 @@ private async Task AskForDolphinNandChoiceAsync() .AwaitAnswer(); if (!useDolphinData) { - dolphinData.SetEnabled(false); + dolphinData.SetSharingEnabled(false); dolphinData.SetCopyEnabled(false); return Ok(); } - dolphinData.SetEnabled(true); var copyNand = await new YesNoWindow() .SetMainText(t("question.recomp_nand_mode.title")) .SetExtraText(t("question.recomp_nand_mode.extra")) @@ -185,6 +187,7 @@ private async Task AskForDolphinNandChoiceAsync() if (!copyNand) { dolphinData.SetCopyEnabled(false); + dolphinData.SetSharingEnabled(true); return Ok(); } @@ -204,6 +207,7 @@ private async Task AskForDolphinNandChoiceAsync() // Flipped only after the copy durably exists, so a failed copy can never leave the settings // pointing at a NAND that is not there. dolphinData.SetCopyEnabled(true); + dolphinData.SetSharingEnabled(false); return Ok(); } @@ -229,11 +233,13 @@ private async Task RunSetupAsync(string goal) var retroRewindResult = await EnsureRetroRewindCurrentAsync(progressWindow, cancellationTokenSource.Token); if (retroRewindResult.IsFailure) - return retroRewindResult.Error; + return IsCancellationRequested(progressWindow, cancellationTokenSource) + ? CancellationWarning("WiiCompiled installation was cancelled.") + : retroRewindResult.Error; var retroRewindCommitted = retroRewindResult.Value; - if (!retroRewindCommitted && (progressWindow.WasCancellationRequested || cancellationTokenSource.IsCancellationRequested)) - return Fail("WiiCompiled update was cancelled."); + if (!retroRewindCommitted && IsCancellationRequested(progressWindow, cancellationTokenSource)) + return CancellationWarning("WiiCompiled installation was cancelled."); if (retroRewindCommitted) { @@ -247,15 +253,21 @@ private async Task RunSetupAsync(string goal) // The Retro Rewind commit is a barrier: once it is durable, the matching recomp // check (and any repair it demands) must run to completion, so it is deliberately // handed an uncancellable token. Whatever the backend reports is the truth. - return await installService.InstallAsync( + var installResult = await installService.InstallAsync( progress, ConfirmOfflineInstallAsync, retroRewindCommitted ? CancellationToken.None : cancellationTokenSource.Token ); + + // A successful terminal result wins a race with Cancel. Otherwise, a cancellation that + // was still allowed at this point is a user-controlled warning, not an unknown error. + return installResult.IsFailure && !retroRewindCommitted && IsCancellationRequested(progressWindow, cancellationTokenSource) + ? CancellationWarning("WiiCompiled installation was cancelled.") + : installResult; } catch (OperationCanceledException) { - return Fail("WiiCompiled update was cancelled."); + return CancellationWarning("WiiCompiled installation was cancelled."); } finally { @@ -263,6 +275,11 @@ private async Task RunSetupAsync(string goal) } } + private static bool IsCancellationRequested(ProgressWindow progressWindow, CancellationTokenSource cancellationTokenSource) => + progressWindow.WasCancellationRequested || cancellationTokenSource.IsCancellationRequested; + + private static OperationError CancellationWarning(string message) => Fail(message, MessageTranslation.Warning_RecompOperationCancelled); + /// /// Asked by the install service only when a Retro Rewind build is needed and the Retro-WFC payload /// service is down. Offline-only is a real choice, not a silent downgrade: the game plays, online diff --git a/WheelWizard/Features/Settings/ISettingsServices.cs b/WheelWizard/Features/Settings/ISettingsServices.cs index a07bcc95a..918cddd88 100644 --- a/WheelWizard/Features/Settings/ISettingsServices.cs +++ b/WheelWizard/Features/Settings/ISettingsServices.cs @@ -48,7 +48,6 @@ public interface ISettingsProperties Setting ENABLE_ANIMATIONS { get; } Setting TESTING_MODE_ENABLED { get; } Setting SAVED_WINDOW_SCALE { get; } - Setting REMOVE_BLUR { get; } Setting RR_REGION { get; } Setting WW_LANGUAGE { get; } Setting NAND_ROOT_PATH { get; } diff --git a/WheelWizard/Features/Settings/SettingsManager.cs b/WheelWizard/Features/Settings/SettingsManager.cs index ae911eb96..ce6b1b1d4 100644 --- a/WheelWizard/Features/Settings/SettingsManager.cs +++ b/WheelWizard/Features/Settings/SettingsManager.cs @@ -40,11 +40,10 @@ IFileSystem fileSystem // Register this first because the path validators use the active frontend mode when deciding // whether Dolphin-only locations may be left blank. ENABLE_RECOMP = RegisterWhWz("EnableRecomp", false); - // A recomp install should continue using the Dolphin NAND Wheel Wizard already manages. - // Users can still opt out in Recomp Settings when they deliberately want private data. - RECOMP_USE_DOLPHIN_DATA = RegisterWhWz("RecompUseDolphinData", true); - // Dolphin advises against other programs touching its NAND in place, so the first install - // offers to copy it instead; this remembers that the user chose the copy. + // Whether WiiCompiled directly shares Dolphin's live NAND. Disabled means private mode; + // private mode uses the imported clone below when one exists, otherwise the runtime default. + RECOMP_USE_DOLPHIN_DATA = RegisterWhWz("RecompUseDolphinData", false); + // Whether private mode was initialized from the Wheel Wizard-owned Dolphin clone. RECOMP_COPY_DOLPHIN_NAND = RegisterWhWz("RecompCopyDolphinNand", false); DOLPHIN_LOCATION = RegisterWhWz( "DolphinLocation", @@ -125,7 +124,6 @@ IFileSystem fileSystem ENABLE_ANIMATIONS = RegisterWhWz("EnableAnimations", true); TESTING_MODE_ENABLED = RegisterWhWz("TestingModeEnabled", false); SAVED_WINDOW_SCALE = RegisterWhWz("WindowScale", 1.0, SettingValues.IsValidWindowScale); - REMOVE_BLUR = RegisterWhWz("REMOVE_BLUR", true); RR_REGION = RegisterWhWz("RR_Region", MarioKartWiiEnums.Regions.None); WW_LANGUAGE = RegisterWhWz("WW_Language", "en", value => SettingValues.WhWzLanguages.ContainsKey((string)value!)); #endregion @@ -229,7 +227,6 @@ IFileSystem fileSystem public Setting ENABLE_ANIMATIONS { get; } public Setting TESTING_MODE_ENABLED { get; } public Setting SAVED_WINDOW_SCALE { get; } - public Setting REMOVE_BLUR { get; } public Setting RR_REGION { get; } public Setting WW_LANGUAGE { get; } diff --git a/WheelWizard/Helpers/HttpClientHelper.cs b/WheelWizard/Helpers/HttpClientHelper.cs index 76e1d5c47..7fa432170 100644 --- a/WheelWizard/Helpers/HttpClientHelper.cs +++ b/WheelWizard/Helpers/HttpClientHelper.cs @@ -1,6 +1,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using WheelWizard.Models; +using WheelWizard.Shared; namespace WheelWizard.Helpers; @@ -34,7 +35,7 @@ public static class HttpClientHelper public static async Task> PostAsync(string url, HttpContent? body, JsonSerializerOptions? options = null) { #if DEBUG - if (!FakeConnectionToInternet) + if (DevelopmentMode.IsEnabled && !FakeConnectionToInternet) return GetErrorResult(new("No internet connection")); #endif @@ -70,7 +71,7 @@ public static async Task> PostAsync(string url, HttpConte public static async Task> GetAsync(string url, JsonSerializerOptions? options = null) { #if DEBUG - if (!FakeConnectionToInternet) + if (DevelopmentMode.IsEnabled && !FakeConnectionToInternet) return GetErrorResult(new("No internet connection")); #endif diff --git a/WheelWizard/Logging.cs b/WheelWizard/Logging.cs index a1b4b8a50..4f2a01e7c 100644 --- a/WheelWizard/Logging.cs +++ b/WheelWizard/Logging.cs @@ -55,15 +55,11 @@ public static void RecreateStaticLogger() private static void LogPlatformInformation() { // ReSharper disable once RedundantAssignment - var modeCheck = "release"; + var modeCheck = Shared.DevelopmentMode.IsEnabled ? "debug" : "release"; // ReSharper disable once ConvertToConstant.Local var osCheck = "unknown"; -#if DEBUG - modeCheck = "debug"; -#endif - #if WINDOWS osCheck = "windows"; #elif LINUX diff --git a/WheelWizard/Resources/Languages/cs.yml b/WheelWizard/Resources/Languages/cs.yml index b0a3f4407..054493771 100644 --- a/WheelWizard/Resources/Languages/cs.yml +++ b/WheelWizard/Resources/Languages/cs.yml @@ -161,7 +161,7 @@ cs: rooms: "Místnosti" miis: "Miiy" text: - made_by_string: | + made_by_string: |- Vytvořili: {$1} a {$2} thanks_translators: "Díky moc všem překladatelům:" @@ -202,16 +202,16 @@ cs: time: days: 1: "1 den" - n: "($1) dny" + n: "{$1} dny" hours: 1: "1 hodina" - n: "($1) hodiny" + n: "{$1} hodiny" minutes: 1: "1 minuta" - n: "($1) minuty" + n: "{$1} minuty" seconds: 1: "1 sekunda" - n: "($1) sekundy" + n: "{$1} sekundy" progress: estimated_time_remaining: "Předpokládaný zbývající čas:" downloading_mb: "Stahování {$1} MB" @@ -362,12 +362,13 @@ cs: dolphin_user_path: "Uživatelská Složka Dolphin" mario_kart_game: "Soubor Hry Mario Kart Wii" dolphin_emulator_exe: "Spustitelný soubor Dolphin Emulator" - remove_blur: "Odstranit Rozmazání" enable_animations: "Zapnout Animace" reinstall: "Reinstalovat RR" open_game_folder: "Otevřít Složku Hry" open_save_folder: "Otevřít Složku Uložení" section: + graphics: "Grafika" + general: "Obecné" language: "Jazyk" wii: "Nastavení Wii" resolution: "Rozlišení" @@ -384,14 +385,13 @@ cs: end_with_exe: "Cesta musí končit .exe" enable_animations: "To aktivuje animace v aplikaci Wheel Wizard" end_with_x: "Cesta může končit:" - wh_wz_language: "Tím se změní pouze jazyk pro Wheel Wizard. Chceš-li změnit jazyk ve hře, musíš to udělat v nastavení hry." optional: "Volitelný" must_set_paths: "Musíš nastavit tyto 3 cesty než začneš hrát Retro Rewind." category: - video: "Video" about: "O tomto" other: "Ostatní" general: "Obecný" + video: "Video" value: language: france: "Francouzština" diff --git a/WheelWizard/Resources/Languages/de.yml b/WheelWizard/Resources/Languages/de.yml index 42de2ea90..84aa1804e 100644 --- a/WheelWizard/Resources/Languages/de.yml +++ b/WheelWizard/Resources/Languages/de.yml @@ -101,7 +101,7 @@ de: no_license: "Keine Lizenz" no_profiles: "Keine Profile" custom: "Benutzerdefiniert" - installed: "Installiert " + installed: "Installiert" action: play: "Spielen" play_offline: "Offline spielen" @@ -167,7 +167,7 @@ de: mii_editor: "Mii Maker" mii_carousel: "Mii Preview" text: - made_by_string: | + made_by_string: |- Erstellt von: {$1} und {$2} thanks_translators: "Vielen Dank an alle Übersetzer:" @@ -244,21 +244,18 @@ de: extra: "Diese Änderung wird in {$1} zurückgesetzt, außer du wendest die Änderung an." move_data: title: "Wheel Wizard Daten verschieben?" - extra: | - Wheel Wizard wird seine Dateien nach - + extra: |- + Wheel Wizard wird seine Datein nach {$1} - verschieben. Das könnte eine Weile dauern. dolphin_found: title: "Dolphin Emulator Ordner gefunden" - extra: | + extra: |- Wenn du nicht weißt, was das alles bedeutet, klick einfach auf „Ja“ :) - Dolphin Emulator-Ordner gefunden. Möchtest du diesen Ordner verwenden? dolphin_program_found: title: "Dolphin Emulator gefunden" - extra: | + extra: |- Wenn du nicht weißt, was das alles bedeutet, klick einfach auf „Ja“ :) Dolphin Emulator gefunden. Möchtest du Folgendes verwenden? @@ -283,8 +280,6 @@ de: dolphin_flatpak: title: "Dolphin Flatpak Installation" extra: "Die Flatpak Dolphin Version scheint nicht installiert zu sein. Möchtest du sie von uns installieren lassen (für deinen eigenen Benutzer)?" - install_mod: - title: "Du bist dabei das Spiel ohne Mods zu starten. Möchtest du deinen My-Stuff Ordner leeren?" search_mod: "Nach Mods suchen..." enter_new_name: title: "Einen neuen Namen eingeben" @@ -317,13 +312,11 @@ de: extra: "Es ist nicht möglich gerade nach Updates zu suchen. Du bist vielleicht nicht mit dem Internet verbunden oder der Server hat gerade Schwierigkeiten." unable_update_wh_wz: extra: - reason_network: | + reason_network: |- Es kann nicht überprüft werden, ob Wheel Wizard auf dem neuesten Stand ist. - Möglicherweise treten derzeit Netzwerkprobleme auf. - reason_location: | + reason_location: |- Wheel Wizard kann nicht aktualisiert werden. Bitte stelle sicher, dass sich die Anwendung in einem Ordner befindet, in den geschrieben werden kann. - Der aktuelle Ordner konnte nicht gefunden werden. title: "Wheel Wizard konnte nicht aktualisiert werden." no_connect_server: @@ -362,9 +355,8 @@ de: extra: failed_update_delete: "Dateien für das Update konnten nicht gelöscht werden. Abbruch." failed_update_apply: "Das Update konnte nicht installiert werden. Abbruch." - invalid_file_path: | + invalid_file_path: |- Ungültiger Dateipfad wurde erkannt. Bitte kontaktiere die Entwickler. - Server-Fehler: {$1} title: "Über das RR Update" restart_admin_fail: @@ -384,15 +376,14 @@ de: title: "Es ist ein Fehler beim abrufen der Mod-Informationen aufgetreten." mod_download_fail: title: "Mod Download ist schiefgelaufen." - extra: "Ein Fehler ist während dem Dowload aufgetreten." + extra: "Ein Fehler ist während dem Dowload aufgetreten: {$1}" message_success: settings_saved: title: "Einstellungen erfolgreich gespeichert!" data_folder_moved: title: "Datenordner aktualisiert." - extra: | - Wheel Wizard's Daten sind jetzt gespeichert in: - + extra: |- + Wheel WIzard's Daten sind jetzt gespeichert in: {$1} rr_up_to_date: title: "Retro Rewind ist auf dem neuesten Stand." @@ -439,12 +430,13 @@ de: mario_kart_game: "Mario Kart Wii Spiel-Datei" dolphin_emulator_exe: "Dolphin Emulator Programm-Datei" make_primary: "Hauptprofil" - remove_blur: "Unschärfe entfernen" enable_animations: "Aktiviere Animationen" reinstall: "RR neu installieren" open_game_folder: "Spielordner öffnen" open_save_folder: "Speicherdatenordner öffnen" section: + graphics: "Grafik" + general: "Allgemein" language: "Sprache" wii: "Wii Einstellungen" resolution: "Auflösung" @@ -465,17 +457,15 @@ de: end_with_exe: "Dateipfad muss mit .exe enden." enable_animations: "Dies aktiviert die Animationen in der Wheel Wizard App." end_with_x: "Pfad kann enden mit:" - wh_wz_language: "Dies ändert nur die Sprache innerhalb von Wheel Wizard. Um die Sprache innerhalb des Spiels zu ändern musst du das in den Spieleinstellungen vornehmen" optional: "Optional" must_set_paths: "Du musst erst diese 3 Dateipfade bestimmen um Retro Rewind spielen zu können." category: - video: "Video" about: "Über" other: "Sonstiges" general: "Allgemein" + video: "Video" status: data_folder: - default: "Standard-Speicherort wird verwendet." custom: "Verwendet benutzerdefinierten Speicherort" moving: "Dateien werden verschoben, bitte warten..." value: diff --git a/WheelWizard/Resources/Languages/en.yml b/WheelWizard/Resources/Languages/en.yml index 67e678861..9cfd1b1e5 100644 --- a/WheelWizard/Resources/Languages/en.yml +++ b/WheelWizard/Resources/Languages/en.yml @@ -107,11 +107,13 @@ en: installed: "Installed" action: play: "Play" + open: "Open" play_offline: "Play Offline" play_anyway: "Play anyway" install: "Install" update: "Update" import: "Import" + clone: "Clone" convert_to_patches: "Convert to patches" enable_all: "Enable All" disable_all: "Disable All" @@ -131,6 +133,7 @@ en: revert: "Revert" maybe_later: "Maybe Later" browse: "Browse" + change: "Change" view_mod: "View Mod" save_external_mii: "Add Mii to \"My Miis\"" view_mii: "View Mii" @@ -140,7 +143,7 @@ en: favorite: "Favorite" view_room: "View Room" recomp_nand_copy: "Make a copy" - recomp_nand_share: "Use directly" + recomp_nand_share: "Share directly" recomp_install_offline: "Install offline-only" link: discord: "Talk to us!" @@ -175,9 +178,9 @@ en: mii_editor: "Mii Editor" mii_carousel: "Mii Carousel" text: - made_by_string: | + made_by_string: |- Made by: {$1} - and {$2} + And {$2} thanks_translators: "Thanks a lot to all the translators:" wh_wz_translation_percentage: "Translations for this language are {$1}% complete" powered_gamebanana: "Powered By GameBanana" @@ -257,6 +260,7 @@ en: preparing_files_count: "Preparing {$1} file(s)" installing_recomp: "Installing WiiCompiled" updating_recomp: "Updating WiiCompiled" + uninstalling_recomp: "Uninstalling WiiCompiled" recomp_checking_release: "Looking for the latest WiiCompiled release" recomp_downloading_setup: "Downloading the WiiCompiled installer" recomp_running_setup: "Running the WiiCompiled installer" @@ -277,6 +281,12 @@ en: recomp_nand_mode: title: "Copy your Dolphin data, or use it directly?" extra: "Dolphin recommends against other programs using its Wii data (NAND) directly, since two programs writing to it at once can corrupt it. A copy keeps WiiCompiled completely separate, but progress made in one will not appear in the other." + recomp_share_dolphin_data: + title: "Share NAND data with Dolphin?" + extra: "Cloning gives WiiCompiled the same current saves, Miis, and Wii settings, but keeps its copy separate and is safer. Direct sharing lets Dolphin and WiiCompiled write to the same NAND, which can corrupt it if both run at once. Continue only if you understand the risk." + recomp_overwrite_dolphin_clone: + title: "Replace the private Dolphin clone?" + extra: "This replaces the current clone with Dolphin's data. Saves, Miis, and Wii settings changed only in WiiCompiled will be lost." launch_clear_mods_found: title: "Mods found" extra: "You are about to launch the game without any mods. Do you want to clear your my-stuff folder?" @@ -289,27 +299,24 @@ en: extra: "This change will revert in {$1} unless you decide to keep the change." move_data: title: "Move Wheel Wizard data?" - extra: | + extra: |- Wheel Wizard will move its files to: - {$1} - This may take a while depending on the amount of data. dolphin_found: title: "Dolphin Emulator Folder Found" - extra: | - If you don't know what all of this means, just click yes :) - + extra: |- + If you dont know what all of this means, just click yes :) Dolphin Emulator folder found. Would you like to use this folder? dolphin_outdated: title: "Your Dolphin Emulator is out of date" - extra: | + extra: |- This version of Dolphin ({$1}) has a known security issue where malicious game files, mods, or cheating online players can infect your PC with malware. Update to {$2} or newer to be safe. dolphin_program_found: title: "Dolphin Emulator Found" - extra: | + extra: |- If you don't know what all of this means, just click yes :) Dolphin Emulator found. Would you like to use this? @@ -348,6 +355,9 @@ en: title: "This will close the current window and open a new one with the new language settings." extra: "Do you want to apply the new language settings?" message_warning: + recomp_operation_cancelled: + title: "WiiCompiled operation cancelled" + extra: "The operation was stopped before it finished. You can start it again whenever you're ready." not_find_dolphin: extra: "Could not find Dolphin Emulator, please set the path in settings" not_find_game: @@ -366,7 +376,7 @@ en: extra: "Dolphin Emulator folder not automatically found. Please try and find the folder manually, click 'help' for more information." dolphin_version_unverified: title: "Could not verify your Dolphin Emulator version" - extra: | + extra: |- Wheel Wizard could not read the version of the Dolphin Emulator you have configured, so it cannot tell whether it has the security fixes. Please check yourself that you are on {$1} or newer. Older versions have a known security issue where malicious game files, mods, or cheating online players can infect your PC with malware. @@ -378,13 +388,11 @@ en: extra: "Can't check for updates right now. You might not be connected to the internet or the server might be down." unable_update_wh_wz: extra: - reason_network: | + reason_network: |- Unable to check if Wheel Wizard is up to date. - You might be experiencing network issues. - reason_location: | + reason_location: |- Unable to update Wheel Wizard. Please ensure the application is located in a folder that can be written to. - Could not find current folder. title: "Unable to update Wheel Wizard." no_connect_server: @@ -425,10 +433,9 @@ en: extra: failed_update_delete: "Failed to delete files for the update. Aborting." failed_update_apply: "Failed to apply an update. Aborting." - invalid_file_path: | + invalid_file_path: |- Invalid file path detected. Please contact the developers. - - Server error: {$1} + Server error: {$1} title: "Aborting RR Update" restart_admin_fail: extra: "Failed to restart with administrator rights." @@ -453,9 +460,8 @@ en: title: "Settings saved successfully!" data_folder_moved: title: "Data folder updated" - extra: | + extra: |- Wheel Wizard data is now stored in: - {$1} rr_up_to_date: title: "Retro Rewind is up to date." @@ -521,6 +527,10 @@ en: force_disable_wiimote: "Force Disable Wiimote" launch_with_dolphin: "Launch Game With Dolphin Window" launch_rr_on_startup: "Launch Retro Rewind On Startup" + retro_rewind_installation: "Retro Rewind Installation" + save_folder: "Save Folder" + installation_folder: "Installation Folder" + recomp_installation: "WiiCompiled Installation" v_sync: "VSync" recommended: "Recommended Settings" show_fps: "Show FPS" @@ -528,22 +538,26 @@ en: window_scale: "Window Scale" open_config: "Open Config Folder" wheel_wizard_data_folder: "Wheel Wizard data folder" - reset_data_folder: "Use default location" + retro_rewind_folder: "Retro Rewind Folder" + reset_data_folder: "Use Default" dolphin_user_path: "Dolphin User Folder" mario_kart_game: "Mario Kart Wii Game File" dolphin_emulator_exe: "Dolphin Emulator executable" make_primary: "Make Primary" - remove_blur: "Remove Blur" enable_animations: "Enable Animations" reinstall: "Reinstall RR" open_game_folder: "Open Game Folder" open_save_folder: "Open Save Folder" enable_recomp: "Enable WiiCompiled (beta)" - recomp_use_dolphin_data: "Share Dolphin save data" + recomp_share_dolphin_data: "Share NAND data with Dolphin" + recomp_clone_dolphin_data: "Clone Dolphin NAND" recomp_resolution: "Resolution" recomp_graphics_api: "Graphics API" recomp_prevent_stutters: "Prevent stutters" section: + graphics: "Graphics" + general: "General" + retro_rewind: "Retro Rewind" language: "Language" wii: "Wii Settings" resolution: "Resolution" @@ -552,28 +566,34 @@ en: location_paths: "Location Settings" wh_wz_appearance: "Wheel Wizard appearance" installation: "Installation" + setup: "Setup" recomp: "WiiCompiled (beta)" - recomp_dolphin_data: "Dolphin Data" recomp_video: "Video" helper_text: topbar: location_warning: "Settings are disabled, complete the location settings first" enable_recomp: "Uses WiiCompiled as the home-page launcher. Turn this off to use Dolphin and Retro Rewind instead. Windows only." - recomp_use_dolphin_data: "Use Dolphin's existing Wii folder in place so WiiCompiled shares saves, Miis, and Wii settings. A change takes effect the next time WiiCompiled launches." - recomp_graphics_api: "Only DirectX 12 and Vulkan run WiiCompiled reliably, so nothing else is offered." + recomp_share_dolphin_data: "WiiCompiled and Dolphin use the same saves, Miis, and Wii settings." + recomp_shared_nand_warning: "Do not run Dolphin and WiiCompiled at the same time while sharing NAND data." + recomp_clone_dolphin_data: "Copies Dolphin's current saves, Miis, and Wii settings into private WiiCompiled data." + recomp_graphics_api: "WiiCompiled works reliably with DirectX 12 and Vulkan." recomp_dolphin_data_not_found: "Set an existing Dolphin User Folder in Location Settings first. It must contain a Wii folder." recomp_prevent_stutters: "While a shader is still compiling, the objects that need it are skipped for a few frames instead of the whole game stalling until it is ready. You get a tiny bit of pop-in instead of a stutter." launch_with_dolphin: "Will launch dolphins main window along with the game" launch_rr_on_startup: "Automatically launches Retro Rewind when Wheel Wizard starts" + installed_version: "Installed version: {$1}" force_disable_wiimote: "This setting disables Wiimote ingame, but enables it for the Wii channel" resolution_scale: "1x is the native resolution" + renderer: "Selects the graphics backend Dolphin uses to render the game." + v_sync: "Synchronizes the game's frame rate with your display to prevent screen tearing." recommended: "This setting will change certain dolphin settings to reduce stuttering and lag spikes" 30_fps: "This setting will force the game to run in 30 FPS (default is 60)" wheel_wizard_data_folder: "Choose where Wheel Wizard stores its files. Moving may take a while." + retro_rewind_folder: "Opens the folder containing the installed Retro Rewind files." end_with_exe: "Path must end with .exe" enable_animations: "This enables the animations in the Wheel Wizard app" end_with_x: "Path can end with:" - wh_wz_language: "This only changes the language for Wheel Wizard. To change the in-game language, you’ll need to do that within the game settings." + wh_wz_language: "Changes the language used by Wheel Wizard, not the in-game language." optional: "Optional" must_set_paths: "You must set these 3 paths before you can start playing Retro Rewind" must_set_game_path: "You must select your Mario Kart Wii game file before you can start playing WiiCompiled" @@ -582,15 +602,15 @@ en: about: "About" other: "Other" general: "General" + video: "Video" status: - recomp_dolphin_data_not_linked: "Not linked. WiiCompiled will use its private Wii data." - recomp_dolphin_data_linked: "Sharing Dolphin data from {$1}" - recomp_dolphin_data_copied: "Using a copy of your Dolphin data at {$1}" + recomp_dolphin_clone_available: "Private clone: {$1}" + recomp_dolphin_clone_missing: "No private clone has been created." + recomp_dolphin_data_cloned: "Dolphin data cloned for WiiCompiled" recomp_dolphin_data_not_found: "Dolphin data not found" recomp_not_installed: "WiiCompiled is not installed yet. Install it from the home page first." recomp_uninstalled: "WiiCompiled has been uninstalled" data_folder: - default: "Using default location." custom: "Using custom location." moving: "Moving data, please wait..." value: @@ -610,5 +630,6 @@ en: czech: "Czech" norwegian: "Norwegian" chinese: "Chinese" + polish: "Polish" portuguese: "Portuguese" z_translators: "-" diff --git a/WheelWizard/Resources/Languages/es.yml b/WheelWizard/Resources/Languages/es.yml index 2ae4e778c..e57cc7995 100644 --- a/WheelWizard/Resources/Languages/es.yml +++ b/WheelWizard/Resources/Languages/es.yml @@ -155,9 +155,7 @@ es: rooms: "Salas" miis: "Miis" text: - made_by_string: | - Hecho por: {$1} - y {$2} + made_by_string: "Hecho por: {$1} y {$2}" thanks_translators: "Muchas gracias a todos los traductores:" wh_wz_translation_percentage: "La traducción de este idioma está a un {$1}%." hover: @@ -224,10 +222,9 @@ es: extra: "Este cambio se revertira en {$1} a menos de que decidas de quedarte con este cambio." dolphin_found: title: "Carpeta del Emulador Dolphin encontrado" - extra: | - Ante la duda, solo marque si. - - Carpeta del emulador Dolphin encontrado. ¿Desea usar esta carpeta? + extra: |- + Ante la duda, solo marque si + . Carpeta del emulador Dolphin encontrado. ¿Desea usar esta carpeta? new_version_wh_wz: title: "Actualización de Wheel Wizard disponible" extra: "La version {$1} de WheelWizzard está disponible (Actualmente en {$2}). ¿Desea actualizar ahora?" @@ -268,13 +265,11 @@ es: extra: "No se pueden buscar actualizaciones en este momento. Es posible que no tengas conexión a internet o que el servidor esté inactivo." unable_update_wh_wz: extra: - reason_network: | + reason_network: |- Incapaz de confirmar la actualidad de su versión de Wheel Wizard. - Podrías estar experimentando problemas de conexión. - reason_location: | + reason_location: |- Incapaz de actualizar Wheel Wizard. Asegurate que la aplicación esta ubicada en una carpeta que se puede editar. - No se encontró la carpeta actual. title: "No se pudo actualizar Wheel Wizard." no_connect_server: @@ -300,9 +295,8 @@ es: extra: failed_update_delete: "Error al borrar archivos para la actualización. Cancelando" failed_update_apply: "Error al aplicar una actualización. Cancelando." - invalid_file_path: | + invalid_file_path: |- Se detectó una ruta de archivo no válida. Contacte a los desarrolladores. - Error del servidor: {$1} title: "Abortando actualización de RR" restart_admin_fail: @@ -361,12 +355,13 @@ es: mario_kart_game: "Archivo del juego Mario Kart Wii" dolphin_emulator_exe: "Emulador Dolphin Ejecutable" make_primary: "Hacer primaria" - remove_blur: "Quitar Blur" enable_animations: "Activar Animaciones" reinstall: "Reinstalar RR" open_game_folder: "Abrir carpeta de juegos" open_save_folder: "Abrir carpeta de archivos de juego guardados" section: + graphics: "Gráficos" + general: "General" language: "Idioma" wii: "Ajustes de Wii" resolution: "Resolución" @@ -386,10 +381,10 @@ es: end_with_exe: "La ruta debe terminar en un .exe" enable_animations: "Esto activa las animaciones en la aplicación Wheel Wizard" category: - video: "Video" about: "Acerca de" other: "Otro" general: "General" + video: "Video" value: language: france: "Francés" diff --git a/WheelWizard/Resources/Languages/fi.yml b/WheelWizard/Resources/Languages/fi.yml index 6cc031b19..432cdfa60 100644 --- a/WheelWizard/Resources/Languages/fi.yml +++ b/WheelWizard/Resources/Languages/fi.yml @@ -3,15 +3,18 @@ fi: home: "Etusivu" settings: "Asetukset" mods: "My Stuff" + patches: "Paikkaukset" my_profiles: "Omat profiilit" rooms: "Huoneet" friends: "Kaverit" + leaderboard: "Tulostaulu" room_details: "Huoneen tiedot" my_miis: "Omat Miit" attribute: status_online: "Online" enabled: "Käytössä" title: "Otsikko" + all: "Kaikki" status_offline: "Offline" name: "Nimi" id: "ID" @@ -108,6 +111,7 @@ fi: install: "Asenna" update: "Päivitä" import: "Tuo" + convert_to_patches: "Muunna paikkauksiksi" enable_all: "Ota kaikki käyttöön" disable_all: "Poista kaikki" rename: "Nimeä uudelleen" @@ -167,13 +171,15 @@ fi: mii_editor: "Mii-editori" mii_carousel: "Miin katselu" text: - made_by_string: | + made_by_string: |- Tehnyt: {$1} ja {$2} thanks_translators: "Suuret kiitokset kaikille kääntäjille:" wh_wz_translation_percentage: "Tämän kielen käännökset ovat {$1} % valmiita." powered_gamebanana: "Palvelun tarjoaa GameBanana" language_translated_by: "Tämän kielen käännökset tarjoaa: {$1}" + external_reference: "Ulkoinen viite" + external_reference_with_path: "Ulkoinen viite: {$1}" hover: players_online: 0: "Ei pelaajia online-tilassa" @@ -192,7 +198,7 @@ fi: Liittyäksesi tiettyyn huoneeseen, se tulee tehdä joko kaverin kautta tai toivoa että pääset siihen liittymällä verkkopeliin. friends_page_disclaimer: "Kavereiden lisääminen täytyy tehdä pelin sisällä." region_user_selection: "Näyttää vain alueet, joilla itse olet pelannut" - primary_profile: "Pääprofiilia käytetään referenssinä Wheel Wizardissa" + primary_profile: "Pääprofiilia käytetään viitteenä Wheel Wizardissa" empty_content: no_profiles: "Sinun on pelattava peliä vähintään kerran nähdäksesi profiilisi listalla" no_friends_title: "Ei kavereita vielä." @@ -223,7 +229,7 @@ fi: downloading_mb: "Ladataan {$1} Mt" installing_mods: "Asennetaan modeja" installing_mods_count: "Asennetaan {$1} modia" - processing_xof_y: "Käsitellään {$1}/{$1} tiedostoa..." + processing_xof_y: "Käsitellään {$1}/{$2} tiedostoa..." update_rr: "Päivitetään Retro Rewindia" latest_wh_wz_github: "Hankitaan uusinta Wheel Wizard -versiota GitHubista" update_wh_wz: "Päivitetään Wheel Wizardia" @@ -231,6 +237,14 @@ fi: installing_rr_first_time: "Asennetaan Retro Rewindia ensimmäistä kertaa" installing_dolphin: "Asennetaan Dolphin-emulaattoria" this_may_take_a_while: "Tässä voi kestää jonkin aikaa riippuen internet-yhteydestäsi." + applying_converted_mod: "Sovelletaan muunnettu modi" + combining_files: "Yhdistetään tiedostoja" + converting_file: "Muunnetaan {$1}" + converting_files_count: "Muunnetaan {$1} tiedostoa" + converting_mod_to_patches: "Muunnetaan modi paikkauksiksi" + downloading_mod: "Ladataan {$1}" + installing_mod: "Asennetaan modia" + preparing_files_count: "Valmistellaan {$1} tiedostoa" question: launch_clear_mods_found: title: "Modeja löytyi" @@ -244,11 +258,16 @@ fi: apply_scale: title: "Haluatko ottaa uuden skaalan käyttöön?" extra: "Muutos perutaan {$1} kuluttua, ellet aio pitää muutosta." + move_data: + title: "Siirretäänkö Wheel Wizardin tiedot?" + extra: |- + Wheel Wizardin tiedostot siirretään sijaintiin: + {$1} + Tässä saattaa mennä hetki riippuen tietojen määrästä. dolphin_found: title: "Dolphin-emulaattorin kansio löytyi" - extra: | + extra: |- Jos et tiedä, mitä kaikki tarkoittaa, paina vain 'Kyllä' :) - Dolphin-emulaattorin kansio löytyi. Haluatko käyttää tätä kansiota? new_version_wh_wz: title: "Wheel Wizard -päivitys saatavilla" @@ -279,6 +298,13 @@ fi: enter_new_name: title: "Anna uusi nimi" extra: "Muutetaan nimeä: {$1}" + enter_mod_name: + title: "Modin nimi:" + launch_clear_patches_found: + extra: "Olet käynnistämässä pelin ilman modeja. Haluatko tyhjentää paikkauskansiosi? Pelin käynnistyy muuten nykyisen paikkauskansion ollessa aktiivinen." + apply_language_settings: + title: "Tämä sulkee ikkunan ja avaa uuden valitulla kieliasetuksella." + extra: "Haluatko ottaa uuden kieliasetuksen käyttöön?" message_warning: not_find_dolphin: extra: "Dolphin-emulaattoria ei löytynyt. Määritä sijaintipolku asetuksissa." @@ -296,18 +322,19 @@ fi: dolphin_not_found: title: "Dolphin-emulaattorin kansiota ei löytynyt" extra: "Dolphin-emulaattorin kansiota ei löytynyt automaattisesti. Yritä etsiä kansio mauaalisesti. Lisätietoa saat napsauttamalla 'Ohje'." + dolphin_tool_selected: + title: "Kelvoton Dolphinin käynnistystiedosto valittuna" + extra: "Sinulla on DolphinTool.exe valittuna. Ole hyvä ja valitse Dolphin.exe (Dolphin-emulaattorin käynnistystiedosto) sen sijaan." fail_check_updates: title: "Päivitysten tarkistus epäonnistui" extra: "Päivityksiä ei voi tarkistaa juuri nyt. Internet-yhteyttä ei ole tai palvelimessa on ongelma." unable_update_wh_wz: extra: - reason_network: | + reason_network: |- Ei voitu tarkistaa, onko Wheel Wizard ajan tasalla. - Syynä saattaa olla verkkoyhteysongelmat. - reason_location: | + reason_location: |- Wheel Wizardia ei voi päivittää. Varmista, että sovellus sijaitsee kansiossa, johon voi kirjoittaa tietoja. - Nykyistä kansiota ei löytynyt. title: "Wheel Wizardia ei voi päivittää." no_connect_server: @@ -330,6 +357,8 @@ fi: unable_download_mod: title: "Modia ei voi ladata" extra: "Tiedostoja ei löytynyt." + could_not_convert_mod: + title: "Modia ei voitu muuntaa" placeholder: enter_mod_name: "Anna modin nimi..." search_mod: "Hae modeja..." @@ -340,13 +369,14 @@ fi: message_error: no_mod_folder: extra: "Modikansiota ei ole olemassa" + data_folder_move: + title: "Tietojen siirto epäonnistui" abort_rr: extra: failed_update_delete: "Tiedostojen poistaminen päivitystä varten epäonnistui. Perutaan." failed_update_apply: "Päivityksen käyttöönotto epäonnistui. Perutaan." - invalid_file_path: | + invalid_file_path: |- Virheellinen polku. Ota yhteyttä kehittäjiin. - Palvelinvirhe: {$1} title: "Perutaan RR-päivitys" restart_admin_fail: @@ -370,9 +400,22 @@ fi: message_success: settings_saved: title: "Asetukset tallennettu!" + data_folder_moved: + title: "Tietokansio päivitetty" + extra: |- + Wheel Wizardin tiedot sijaitsevat nyt kohteessa: + {$1} rr_up_to_date: title: "Retro Rewind on ajan tasalla." mii_changed: "Mii vaihdettu!" + mod_converted_to_patches: + title: "Muunnettu paikkauksiksi" + mod_installed: + extra: "Modi '{$1}' asennettu." + title: "Modi asennettu!" + patch_conversion_result: "{$1} tiedostoa muunnettu {$2} paikkaustiedostoksi." + patch_conversion_skipped: "{$1} tiedostoa ei muutettu, koska niitä ei voitu muuntaa." + patch_conversion_notes: "Huomautus: {$1}" snackbar_warning: cant_save_mii: "Miitä ei voi tallentaa" no_mii_export: "Vietäviä Miitä ei ole" @@ -397,27 +440,38 @@ fi: mii_failure_duplicate: "Miin '{$1}' kopioiminen epäonnistui" helper_note: name_must_between: "Nimien täytyy olla 3–10 merkkiä pitkä." + creator_name_less11: "Tekijän nimen täytyy olla alle 11 merkkiä pitkä." creator_name_less10: "Tekijän nimen täytyy olla alle 10 merkkiä pitkä." + file_picker: + select_mod_file: "Valittu moditiedosto" + patch: + incompatible_mod: + message: "Tämä modi on yhteensopimaton muiden modien kanssa. Pelin käynnistäminen tämän modin kanssa voi aiheuttaa virheellistä toimintaa tai kaatumisia. Harkitse modin muuntamista paikkauksiksi napsauttamalla sitä hiiren oikealla painikkeella." + title: "YHTEENSOPIMATON MODI" option: wh_wz_language: "Wheel Wizardin kieli" force_disable_wiimote: "Pakota Wiimote pois käytöstä" launch_with_dolphin: "Käynnistä peli Dolphin-ikkunassa" + launch_rr_on_startup: "Käynnistä Retro Rewind heti" v_sync: "Pystytahdistus (V-Sync)" recommended: "Suositellut asetukset" show_fps: "Näytä kuvalaskuri" renderer: "Renderöijä" window_scale: "Ikkunan koko" open_config: "Avaa asetuskansio" + wheel_wizard_data_folder: "Wheel Wizardinin tietokansio" + reset_data_folder: "Käytä oletussijaintia" dolphin_user_path: "Dolphin-käyttäjäkansio" mario_kart_game: "Mario Kart Wii -pelitiedosto" dolphin_emulator_exe: "Dolphin-emulaattorin käynnistystiedosto" make_primary: "Aseta ensisijaiseksi" - remove_blur: "Poista sumennus" enable_animations: "Animaatiot" reinstall: "Asenna RR uudelleen" open_game_folder: "Avaa pelikansio" open_save_folder: "Avaa tallennuskansio" section: + graphics: "Grafiikka" + general: "Yleiset" language: "Kieli" wii: "Wii-asetukset" resolution: "Resoluutio" @@ -430,21 +484,26 @@ fi: topbar: location_warning: "Asetukset on lukittu, suorita sijaintipolkujen asetukset ensin" launch_with_dolphin: "Käynnistää Dolphinin pääikkunan pelin mukana" + launch_rr_on_startup: "Retro Rewind käynnistetään heti kun Wheel Wizard käynnistyy" force_disable_wiimote: "Poistaa Wiimoten käytöstä pelissä, mutta ottaa sen käyttöön Wii-valikossa" resolution_scale: "1x on alkuperäinen resoluutio" recommended: "Muuttaa joitakin Dolphinin asetuksia pätkimisen ja lagipiikkien vähentämiseksi" 30_fps: "Pakottaa pelin toimimaan 30 FPS -tilassa (oletus on 60)" + wheel_wizard_data_folder: "Valitse Wheel Wizardin tiedostosijainti. Siirtäminen voi kestää hetken." end_with_exe: "Polun täytyy loppua päätteeseen .exe" enable_animations: "Ottaa käyttöön animaatiot Wheel Wizard -sovelluksessa" end_with_x: "Polku voi loppua:" - wh_wz_language: "Vaihtaa kielen vain Wheel Wizard -sovelluksessa. Pelin kielen saa vaihdettua itse pelin sisällä." optional: "Valinnainen" must_set_paths: "Nämä 3 polkua on määritettävä ennen kuin voit pelata Retro Rewindia" category: - video: "Grafiikka" about: "Tietoa" other: "Muut" general: "Yleiset" + video: "Grafiikka" + status: + data_folder: + custom: "Käytetään mukautettua sijaintia." + moving: "Siirretään tietoja, odota hetki..." value: language: france: "Ranska" @@ -463,4 +522,4 @@ fi: norwegian: "Norja" chinese: "Kiina" portuguese: "Portugali" - z_translators: "Clavey" + z_translators: "Clavey, Alokas" diff --git a/WheelWizard/Resources/Languages/fr.yml b/WheelWizard/Resources/Languages/fr.yml index 2ea9a36f5..390e18997 100644 --- a/WheelWizard/Resources/Languages/fr.yml +++ b/WheelWizard/Resources/Languages/fr.yml @@ -9,7 +9,7 @@ fr: room_details: "Détail de la salle" my_miis: "Mes Miis" attribute: - status_online: "En Ligne " + status_online: "En Ligne" enabled: "Activer" title: "Titre" status_offline: "Hors-Ligne" @@ -32,7 +32,7 @@ fr: gender: "Genre" gender_female: "Femme" vertical_position: "Position verticale" - size: "Taille " + size: "Taille" nose_type: "Type de nez" horizontal_position: "Position horizontale" mouth_type: "Type de bouches" @@ -61,7 +61,7 @@ fr: status: "Status" priority: "Priorité" sort_by: "Trier par" - total_games_won: "Nombre de courses gagnées " + total_games_won: "Nombre de courses gagnées" total_games_played: "Nombre de courses faites" vr_abbreviation: "PTS.C" br_abbreviation: "PTS.B" @@ -90,14 +90,14 @@ fr: loading: "Chargement..." installing: "Installation..." updating: "Mise à jour..." - config_not_finished: "Configuration non terminée " - no_server: "Aucun Seveurs " + config_not_finished: "Configuration non terminée" + no_server: "Aucun Seveurs" unknown: "Inconnu" error: "Erreur" warning: "Avertisemment" - success: "Succès " + success: "Succès" extracting: "Extraction des fichers..." - no_name: "Aucun nom " + no_name: "Aucun nom" no_license: "Aucun Permis" no_profiles: "Aucun Profil" custom: "Personnalisé" @@ -105,14 +105,14 @@ fr: action: play: "Jouer" play_offline: "Jouer en Hors-Ligne" - install: "Installer " + install: "Installer" update: "Mettre à Jour" import: "Importer" enable_all: "Tous Activer" disable_all: "Tous Désactiver" rename: "Renommer" delete: "Supprimer" - open_folder: "Ouvrir le dossier " + open_folder: "Ouvrir le dossier" edit: "Modifer" cancel: "Annuler" save: "Sauvegarder" @@ -123,13 +123,13 @@ fr: no: "Non" keep: "Garder" ok: "Ok" - revert: "Revenir en arrière " - maybe_later: "Peut être plus tard " + revert: "Revenir en arrière" + maybe_later: "Peut être plus tard" browse: "Chercher" view_mod: "Détail du Mod" save_external_mii: "Ajouter un Mii dans \"Mes Miis\"" view_mii: "Voir le Mii" - duplicate: "Dupliquer " + duplicate: "Dupliquer" back: "Retour" unfavorite: "Retirer des favoris" favorite: "Favoris" @@ -167,7 +167,7 @@ fr: mii_editor: "Éditeur de Mii" mii_carousel: "Vue d'ensemble du Mii" text: - made_by_string: | + made_by_string: |- Fait par: {$1} et {$2} thanks_translators: "Merci à tous les traducteurs:" @@ -207,7 +207,7 @@ fr: no_mod_selected: "Sélectionner un mod dans la liste pour voir ces détails" time: days: - 1: "1 Jour " + 1: "1 Jour" n: "{$1} Jours" hours: 1: "1 heure" @@ -219,7 +219,7 @@ fr: 1: "1 seconde" n: "{$1} secondes" progress: - estimated_time_remaining: "Temps restant estimé: " + estimated_time_remaining: "Temps restant estimé:" downloading_mb: "Téléchargement de {$1} MB" installing_mods: "Installation de mods" installing_mods_count: "Installation de {$1} mods" @@ -227,51 +227,50 @@ fr: update_rr: "Mise à jour de Retro Rewind en cours..." latest_wh_wz_github: "Obtenez la dernière version de Wheel Wizard depuis Github" update_wh_wz: "Mise à jour de Wheel Wizard en cours..." - installing_rr: "Installation de Retro Rewind " + installing_rr: "Installation de Retro Rewind" installing_rr_first_time: "Installation de Retro Rewind pour la première fois" installing_dolphin: "Installation de Dolphin Emulator" this_may_take_a_while: "Cela peut prendre plus de temps en fonction de votre connexion internet." question: launch_clear_mods_found: - title: "Mods trouvés " + title: "Mods trouvés" extra: |- Vous êtes sur le point de lancer le jeu sans aucun mods. - Voulez vous supprimer votre dossier My-Stuff ? + Voulez vous supprimer votre dossier My-Stuff ? sure_delete: title: "Êtes-vous sûr de vouloir supprimer {$1} ?" extra: "La suppresion est permanente et ne peut pas être inversée." title_miis: "Êtes-vous sûr de vouloir supprimer {$1} Miis ?" apply_scale: - title: "Voulez-vous appliquez la nouvelle échelle ? " + title: "Voulez-vous appliquez la nouvelle échelle ?" extra: "Cette modification va être rétablie dans {$1} sauf si vous choisissez de garder cette modification." dolphin_found: title: "Dossier Dolphin Emulator trouvé" - extra: | + extra: |- Si vous ne savez pas ce que tous cela veut dire, juste cliquez sur oui :) - Dossier Dolphin Emulator trouvé. Voulez-vous utilser ce dossier ? new_version_wh_wz: title: "Mise à jour Wheel Wizard disponible !" - extra: "La version {$1} de Wheel Wizard est disponible (Version actuelle: {$2}). Voulez-vous mettre à jour maintenant ? " + extra: "La version {$1} de Wheel Wizard est disponible (Version actuelle: {$2}). Voulez-vous mettre à jour maintenant ?" update_admin: title: "Mettre à jour en Administrateur" - extra: "Des fois, une mise à jour demande les droits d'admin. Voulez-vous les activer pour cette mise à jour ? " + extra: "Des fois, une mise à jour demande les droits d'admin. Voulez-vous les activer pour cette mise à jour ?" reinstall_rr: - title: "Réinstaller Retro Rewind " + title: "Réinstaller Retro Rewind" extra: "Êtes-vous sûr de vouloir réinstaller Retro Rewind ?" old_rksys_found: - title: "Ancien rksys.dat trouvé " - extra: "Une ancienne sauvegarde a été trouvée. Voulez-vous l'utiliser ? (Recommandé) " + title: "Ancien rksys.dat trouvé" + extra: "Une ancienne sauvegarde a été trouvée. Voulez-vous l'utiliser ? (Recommandé)" rr_to_old: extra: |- Votre version de Retro Rewind est trop vieille pour être mis à jour. - Voulez-vous réinstaller Retro Rewind ? + Voulez-vous réinstaller Retro Rewind ? title: "Votre version de Retro Rewind est trop vieille." rr_not_determined: - title: "Télécharger Retro Rewind " + title: "Télécharger Retro Rewind" extra: |- Votre version de Retro Rewind n'a pas pu être déterminée. - Voulez-vous installer Retro Rewind ? + Voulez-vous installer Retro Rewind ? dolphin_flatpak: title: "Installation de Dolphin Flatpak..." extra: "La version Flatpak de Dolphin Emulator ne semble pas être installée. Veuillez-vous qu'on vous l'installe ?" @@ -296,22 +295,20 @@ fr: extra: "Vérifiez bien si tous les chemins sont corrects et essayez à nouveau." title: "Chemin invalide" dolphin_not_found: - title: "Dossier Dolphin Emulator non trouvé " + title: "Dossier Dolphin Emulator non trouvé" extra: "Le dossier Dolphin Emulator n'a pas été automatiquement trouvé. Veuillez essayé de trouver le dossier manuellement, cliquez sur \"Aide\" pour plus d'information." fail_check_updates: title: "Échec de la vérification des mises à jour" extra: "Impossibilité de vérifier les mises à jour actuellement. Vous n'êtes probablement pas connecté à internet ou le serveur est probablement en maintenance" unable_update_wh_wz: extra: - reason_network: | + reason_network: |- Impossible de vérifier si Wheel Wizard est mis à jour. - Vous pourrez expérimenter des problèmes d'internet. - reason_location: | + reason_location: |- Impossible de mettre à jour Wheel Wizard. Veuillez bien vérifier que l'application se trouve dans un dossier qui peut être écrit. - N'a pas pu trouver le dossier actuel. - title: "Impossible de mettre à jour Wheel Wizard. " + title: "Impossible de mettre à jour Wheel Wizard." no_connect_server: extra: "N'a pas pu de se connecter au serveur. Veuillez réessayer plus tard." title: "N'a pas pu se connecter au serveur" @@ -346,10 +343,9 @@ fr: extra: failed_update_delete: "Échec de la désinstallation de fichiers pour la mise à jour. Annulation" failed_update_apply: "Échec d'application de la mise à jour. Annulation." - invalid_file_path: | + invalid_file_path: |- Chemin vers un fichier incorrect. Veuillez contacter les développeurs de Wheel Wizard. - - Erreur Serveur: {$1} + Erreur Serveur: {$1} title: "Anuller la mise à jour de RR" restart_admin_fail: extra: "Échec du redémarrage avec les droits d'administrateur." @@ -360,7 +356,7 @@ fr: failed_change_name: title: "Échec du changement de nom" failed_change_mii: - title: "Échec du changement de Mii " + title: "Échec du changement de Mii" failed_install_dolphin: title: "Échec de l'installation de Dolphin" extra: "L'installation de Dolphin Emulator a échoué. Veuillez essayer d'installer Flatpak Dolphin." @@ -378,13 +374,13 @@ fr: snackbar_warning: cant_save_mii: "Impossible de savegarder le Mii" no_mii_export: "Il semblerait qu'aucun Miis peuvent être exporter" - no_mii_delete: "Il semblerait qu'aucun Miis peuvent être supprimer " + no_mii_delete: "Il semblerait qu'aucun Miis peuvent être supprimer" snackbar_success: - mii_added: "Le Mii a été ajouté a vos Miis " + mii_added: "Le Mii a été ajouté a vos Miis" copied_fc: "Code Ami copié dans le presse-papier" name_change: "Changement du nom en '{$1}' avec succès" saved_mii: "Mii '{$1}' sauvegardé au chemin suivant '{$2}'" - deleted: "Suppression de '{$1}' " + deleted: "Suppression de '{$1}'" deleted_miis: "Suppression de {$1} Miis" created_duplicate: "Clonage de '{$1}'" created_duplicates_miis: "Clonage de {$1} Miis" @@ -404,7 +400,7 @@ fr: option: wh_wz_language: "Langue de Wheel Wizard" force_disable_wiimote: "Forcer la désactivation des Wiimotes" - launch_with_dolphin: "Lancer le jeu avec la fennêtre de Dolphin " + launch_with_dolphin: "Lancer le jeu avec la fennêtre de Dolphin" v_sync: "Synchronisation Verticale" recommended: "Paramètres Recommandés" show_fps: "Montrer les FPS" @@ -415,17 +411,18 @@ fr: mario_kart_game: "ISO Mario Kart Wii" dolphin_emulator_exe: "Éxécutable Dolphin Emulator" make_primary: "Défini par défaut" - remove_blur: "Retirer le Blur" enable_animations: "Activer les animations" - reinstall: "Réinstaller Retro Rewind " + reinstall: "Réinstaller Retro Rewind" open_game_folder: "Ouvrir le dossier du jeu" open_save_folder: "Ouvrir le dossier de sauvegarde" section: + graphics: "Graphismes" + general: "Général" language: "Langue" wii: "Paramètres Wii" resolution: "Résolution" performance: "Paramètres de perfomance" - renderer: "Paramètres de rendu " + renderer: "Paramètres de rendu" location_paths: "Paramètres de localisation" wh_wz_appearance: "Apparence de Wheel Wizard" installation: "Installation" @@ -435,19 +432,18 @@ fr: launch_with_dolphin: "Ira lancer la fenêtre d'accueil de Dolphin en plus du jeu" force_disable_wiimote: "Ce paramètre ira désactiver les Wiimotes dans le jeu, mais les activés dans le menu Wii" resolution_scale: "1x est la résolution native" - recommended: "Ce paramètre va changer certain paramètre Dolphin pour réduire les saccades et lag spikes " + recommended: "Ce paramètre va changer certain paramètre Dolphin pour réduire les saccades et lag spikes" 30_fps: "Ce paramètre va forcer le jeu à tourner en 30 FPS (60 est par défaut)" end_with_exe: "Le chemin doit se finir en .exe" enable_animations: "Ceci active les animations dans l'application Wheel Wizard" end_with_x: "Le chemin doit se finir avec:" - wh_wz_language: "Ceci change uniquement la langue dans Wheel Wizard. Pour changer la langue dans le jeu. Veuillez le faire depuis les paramètres de Retro Rewind." optional: "Optionnel" must_set_paths: "Vous devez impérativement définir ces 3 chemins afin de jouer à Retro Rewind" category: - video: "Vidéo" about: "À propos" other: "Autre" general: "Général" + video: "Vidéo" value: language: france: "Français" @@ -464,6 +460,6 @@ fr: arabic: "Arabe" czech: "Tchèque" norwegian: "Norvégien" - chinese: "Chinois " + chinese: "Chinois" portuguese: "Portugais" z_translators: "Eppe, Yawshi" diff --git a/WheelWizard/Resources/Languages/it.yml b/WheelWizard/Resources/Languages/it.yml index 7dd4dbfa8..58faf16c7 100644 --- a/WheelWizard/Resources/Languages/it.yml +++ b/WheelWizard/Resources/Languages/it.yml @@ -167,7 +167,7 @@ it: mii_editor: "Creatore Mii" mii_carousel: "Visualizzatore Mii" text: - made_by_string: | + made_by_string: |- Fatto da: {$1} e {$2} thanks_translators: "Grazie a tutti i traduttori:" @@ -246,9 +246,8 @@ it: extra: "Questa modifica si ripristinerà in {$1} a meno che tu decida di mantenerla." dolphin_found: title: "Cartella dell'Emulatore Dolphin trovata" - extra: | + extra: |- Se non sai cosa ciò vuol dire, clicca su sì :) - Cartella dell'Emulatore Dolphin trovata. Vuoi usare questa cartella? new_version_wh_wz: title: "Aggiornamento di Wheel Wizard disponibile" @@ -299,13 +298,11 @@ it: extra: "Impossibile verificare gli aggiornamenti in questo momento. Potresti non avere una connessione ad internet oppure il server potrebbe essere offline." unable_update_wh_wz: extra: - reason_network: | + reason_network: |- Impossibile verificare se Wheel Wizard è aggiornato. - Potresti avere problemi di connessione. - reason_location: | + reason_location: |- Impossibile aggiornare Wheel Wizard. Assicurati che l'applicazione si trovi in una cartella con diritti di scrittura. - Non è possibile trovare la cartella attuale. title: "Impossibile aggiornare Wheel Wizard." no_connect_server: @@ -342,9 +339,8 @@ it: extra: failed_update_delete: "Fallito il cancellamento dei file dell'aggiornamento. Annullamento." failed_update_apply: "Fallita l'applicazione di un aggiornamento. Annullamento." - invalid_file_path: | + invalid_file_path: |- Incontrato un percorso file invalido. Contatta gli sviluppatori. - Errore server: {$1} title: "Annullando l'aggiornamento di RR" restart_admin_fail: @@ -411,12 +407,13 @@ it: mario_kart_game: "File di gioco di Mario Kart Wii" dolphin_emulator_exe: "Eseguibile Emulatore Dolphin" make_primary: "Rendi di default" - remove_blur: "Rimuovere la sfocatura (Blur)" enable_animations: "Attiva le animazioni" reinstall: "Reinstalla Retro Rewind" open_game_folder: "Apri la cartella del gioco" open_save_folder: "Apri la cartella del salvataggio" section: + graphics: "Grafica" + general: "Generali" language: "Lingua" wii: "Impostazioni Wii" resolution: "Risoluzione" @@ -436,14 +433,13 @@ it: end_with_exe: "L'indirizzo deve terminare con .exe" enable_animations: "Questa impostazione abiliterà le animazioni nell'applicazione Wheel Wizard" end_with_x: "L'indirizzo può terminare con:" - wh_wz_language: "Questo cambia soltanto la lingua di Wheel Wizard. Per cambiare la lingua all'interno del gioco, dovrai farlo dalle impostazioni del gioco stesso." optional: "Opzionale" must_set_paths: "Devi impostare questi 3 indirizzi prima di poter iniziare a giocare a Retro Rewind" category: - video: "Video" about: "A riguardo" other: "Altro" general: "Generale" + video: "Video" value: language: france: "Francese" diff --git a/WheelWizard/Resources/Languages/ja.yml b/WheelWizard/Resources/Languages/ja.yml index 4cf52a9c7..9e86364b9 100644 --- a/WheelWizard/Resources/Languages/ja.yml +++ b/WheelWizard/Resources/Languages/ja.yml @@ -167,7 +167,7 @@ ja: mii_editor: "Miiエディター" mii_carousel: "Miiスライダー" text: - made_by_string: | + made_by_string: |- 作成者: {$1} と {$2} thanks_translators: "すべての翻訳者さんに感謝します:" @@ -246,9 +246,8 @@ ja: extra: "変更の保存を選択しないと、この変更は{$1}で変更前に戻ります" dolphin_found: title: "Dolphinエミュレーターのフォルダーが見つかりました" - extra: | + extra: |- 意味がよく分からなければ、「はい」をクリックしてください。 - Dolphinエミュレーターのフォルダーが見つかりました。このフォルダーを使用しますか? new_version_wh_wz: title: "Wheel Wizardのアップデートがあります" @@ -301,13 +300,11 @@ ja: extra: "現在アップデートを確認できません。インターネットに接続されていないか、サーバーがダウンしている可能性があります" unable_update_wh_wz: extra: - reason_network: | + reason_network: |- Wheel Wizardが最新かどうか確認できません。 - ネットワークの問題が発生している可能性があります。 - reason_location: | + reason_location: |- Wheel Wizardが更新できません。アプリが書き込み可能なフォルダーにあることを確認してください。 - 現在のフォルダーが見つかりません。 title: "Wheel Wizardのアップデートを無効にする" no_connect_server: @@ -344,10 +341,9 @@ ja: extra: failed_update_delete: "アップデートファイルの削除に失敗しました。中止しています。" failed_update_apply: "アップデートの適用に失敗しました。中止しています。" - invalid_file_path: | + invalid_file_path: |- 無効なファイルパスが検知されました。開発者に連絡してください。 - - サーバーエラー: {$1} + サーバーエラー: {$1} title: "Retro Rewindのアップデートを中止" restart_admin_fail: extra: "管理者権限で再起動するのに失敗しました。" @@ -413,12 +409,13 @@ ja: mario_kart_game: "マリオカートWiiのゲームファイル" dolphin_emulator_exe: "Dolphinエミュレーターを実行可能にする" make_primary: "このライセンスに切り替える" - remove_blur: "ブラー効果の無効化" enable_animations: "アニメーションを有効にする" reinstall: "Retro Rewindの再インストール" open_game_folder: "ゲームフォルダーを開く" open_save_folder: "セーブフォルダーを開く" section: + graphics: "グラフィックス" + general: "一般" language: "言語" wii: "Wii設定" resolution: "解像度" @@ -438,14 +435,13 @@ ja: end_with_exe: "パスの拡張子は必ず .exe にしてください" enable_animations: "Wheel Wizard内のアニメーションを有効にします" end_with_x: "パスは次のもので終わります:" - wh_wz_language: "これはWheel Wizardの言語のみ変更します。ゲーム内の言語を変更したい場合、ゲーム設定内で変更する必要があります" optional: "任意" must_set_paths: "Retro Rewindをプレイする前に、これらの3つのパスを設定する必要があります" category: - video: "映像" about: "情報" other: "その他" general: "一般" + video: "映像" value: language: france: "フランス語" diff --git a/WheelWizard/Resources/Languages/ko.yml b/WheelWizard/Resources/Languages/ko.yml index 900254dd5..5b077a2d8 100644 --- a/WheelWizard/Resources/Languages/ko.yml +++ b/WheelWizard/Resources/Languages/ko.yml @@ -167,7 +167,7 @@ ko: mii_editor: "Mii 에디터" mii_carousel: "Mii 슬라이더" text: - made_by_string: | + made_by_string: |- 만든이: {$1} 과 {$2} thanks_translators: "모든 번역가 분들에게 감사드립니다:" @@ -238,16 +238,15 @@ ko: sure_delete: title: "{$1}을/를 지우시겠습니까?" extra: "삭제는 영구적이며, 되돌릴 수 없습니다" - title_miis: "{1}Mii를 삭제하시겠습니까?" + title_miis: "{$1}Mii를 삭제하시겠습니까?" apply_scale: title: "새로운 해상도를 적용하시겠습니까?" extra: "변경 사항 저장을 선택하지 않으면 변경 전인 {$1}로 돌아갑니다" dolphin_found: title: "돌핀 에뮬레이터 폴더를 찾았습니다" - extra: | + extra: |- 돌핀 에뮬레이터 폴더를 찾았습니다. 해당 폴더를 사용하시겠습니까? - - 뭔 소린지 모르겠다면 그냥 \"네\"를 누르세요 ^_^ + 뭔 소린지 모르겠다면 그냥 "네"를 누르세요 ^_^ new_version_wh_wz: title: "휠 위저드의 새로운 업데이트가 있습니다." extra: "휠 위저드 최신 버전 {$1}이 있습니다.(현재 버전 {$2}). 업데이트 하시겠습니까?" @@ -297,13 +296,11 @@ ko: extra: "업데이트를 확인 할 수 없습니다. 인터넷 연결이 끊어졌거나 서버가 다운된 상태일 수 있습니다" unable_update_wh_wz: extra: - reason_network: | + reason_network: |- 휠 위저드의 버전을 확인할 수 없습니다. - 네트워크 문제가 발생했을 수 있습니다. - reason_location: | + reason_location: |- 휠 위저드를 업데이트할 수 없습니다. 프로그램이 쓰기 가능한 폴더에 있는지 확인하십시오. - 현재 폴더를 찾을 수 없습니다. title: "휠 위저드를 업데이트 할 수 없습니다" no_connect_server: @@ -340,10 +337,9 @@ ko: extra: failed_update_delete: "업데이트 파일 삭제 실패, 중단합니다." failed_update_apply: "업데이트 적용 실패, 중단합니다." - invalid_file_path: | + invalid_file_path: |- 잘못된 파일 경로가 감지됐습니다. 개발자에게 연락하세요. - - 서버 에러: {$1} + 서버 에러: {$1} title: "레트로 리와인드 업데이트 중단" restart_admin_fail: extra: "관리자 권한으로 재시작하는 데 실패했습니다." @@ -362,7 +358,7 @@ ko: title: "모드 정보 불러오기 실패" mod_download_fail: title: "모드 다운로드 실패" - extra: "다운로드 중 오류가 발생했습니다: ($1)" + extra: "다운로드 중 오류가 발생했습니다: {$1}" message_success: settings_saved: title: "설정을 저장했습니다!" @@ -409,12 +405,13 @@ ko: mario_kart_game: "마리오 카트 Wii 게임 파일" dolphin_emulator_exe: "Dolphin 에뮬레이터 실행 가능" make_primary: "이 프로필을 우선으로" - remove_blur: "블러 제거" enable_animations: "애니메이션 활성" reinstall: "레트로 리와인드 재설치" open_game_folder: "게임 폴더 열기" open_save_folder: "세이브 폴더 열기" section: + graphics: "그래픽" + general: "일반" language: "언어" wii: "Wii 설정" resolution: "해상도" @@ -434,14 +431,13 @@ ko: end_with_exe: "파일 경로는 \".exe\"로 끝나야 합니다." enable_animations: "Wheel Wizard 앱의 애니메이션을 활성화 합니다." end_with_x: "파일 경로는 다음과 같이 끝날 수 있습니다:" - wh_wz_language: "이 설정은 Wheel Wizard의 언어만 변경합니다. 게임의 언어를 변경하기 위해서는 게임 설정에서 변경해야 합니다." optional: "옵션" must_set_paths: "Retro Rewind 실행 전 3개의 경로를 설정해야 합니다." category: - video: "비디오" about: "정보" other: "기타" general: "일반" + video: "비디오" value: language: france: "프랑스어" diff --git a/WheelWizard/Resources/Languages/nl.yml b/WheelWizard/Resources/Languages/nl.yml index 6b3525f19..7ecbf8b3c 100644 --- a/WheelWizard/Resources/Languages/nl.yml +++ b/WheelWizard/Resources/Languages/nl.yml @@ -167,7 +167,7 @@ nl: mii_editor: "Mii-editor" mii_carousel: "Mii-carrousel" text: - made_by_string: | + made_by_string: |- Gemaakt door {$1} en {$2} thanks_translators: "Hartelijk dank aan alle vertalers:" @@ -244,9 +244,8 @@ nl: extra: "Deze wijziging wordt over {$1} teruggedraaid, tenzij je ervoor kiest om de wijziging te behouden." dolphin_found: title: "Dolphin-emulator-map gevonden" - extra: | + extra: |- Als je niet weet wat dit allemaal betekent, druk op ja :) - Dolphin-emulator-map gevonden. Wil je deze gebruiken? new_version_wh_wz: title: "Wheel Wizard-update beschikbaar" @@ -297,13 +296,11 @@ nl: extra: "Kan momenteel niet op updates controleren. Mogelijk heb je geen internetverbinding of de server is offline." unable_update_wh_wz: extra: - reason_network: | + reason_network: |- Kan niet controleren of Wheel Wizard up-to-date is. - Er zijn mogelijk netwerkproblemen. - reason_location: | + reason_location: |- Wheel Wizard kan niet worden geüpdatet. Zorg ervoor dat de applicatie zich in een map bevindt waarnaar kan worden geschreven. - Kan de huidige map niet vinden. title: "Wheel Wizard kan niet worden geüpdatet." no_connect_server: @@ -340,9 +337,8 @@ nl: extra: failed_update_delete: "Het is niet gelukt om bestanden te verwijderen voor de update. Proces word afgebroken." failed_update_apply: "Het is niet gelukt om te updaten. Proces word afgebroken." - invalid_file_path: | + invalid_file_path: |- Ongeldige bestandslocatie gedetecteerd. Neem contact op met de ontwikkelaars. - Serverfout: {$1} title: "RR-update afgebroken." restart_admin_fail: @@ -409,12 +405,13 @@ nl: mario_kart_game: "Mario Kart Wii-spelbestand" dolphin_emulator_exe: "Dolphin-emulator-executable" make_primary: "Maak hoofdprofiel" - remove_blur: "Verwijder waas" enable_animations: "Animaties inschakelen" reinstall: "Herinstalleer RR" open_game_folder: "Open spelmap" open_save_folder: "Open opslagmap" section: + graphics: "Grafisch" + general: "Algemeen" language: "Taal" wii: "Wii-Instellingen" resolution: "Resolutie" @@ -434,14 +431,13 @@ nl: end_with_exe: "Locatie moet eindigen met \".exe\"." enable_animations: "Dit schakelt de animaties in de Wheel Wizard-app in." end_with_x: "Locatie kan eindigen met:" - wh_wz_language: "Dit verandert alleen de taal van Wheel Wizard. Om de taal in het spel zelf te wijzigen, moet je dit doen via de instellingen van het spel." optional: "Optioneel" must_set_paths: "Je moet deze 3 locaties instellen voordat je Retro Rewind kunt spelen." category: - video: "Video" about: "Over" other: "Overig" general: "Algemeen" + video: "Video" value: language: france: "Frans" diff --git a/WheelWizard/Resources/Languages/pl.yml b/WheelWizard/Resources/Languages/pl.yml new file mode 100644 index 000000000..b493d7e36 --- /dev/null +++ b/WheelWizard/Resources/Languages/pl.yml @@ -0,0 +1,477 @@ +pl: + page_title: + home: "Strona główna" + settings: "Ustawienia" + mods: "My Stuff" + patches: "Poprawki" + my_profiles: "Moje Profile" + rooms: "Pokoje" + friends: "Przyjaciele" + leaderboard: "Tabela Wyników" + room_details: "Szczegóły Pokoju" + my_miis: "Moje Mii" + attribute: + status_online: "Online" + enabled: "Włączone" + title: "Tytuł" + all: "Wszystko" + status_offline: "Offline" + name: "Nazwa" + id: "ID" + type: "Typ" + speed: "Prędkość" + mod_name: "Nazwa Moda" + average_room_vr: "Średni OV*" + losses: "Przegrane" + wins: "Wygrane" + races_played: "Wyścigi zagrane" + friend_code: "Kod Przyjaciela" + player_count: "Graczy" + time_online: "Czas Online" + message: "Wiadomość" + is_online: "Jest Online" + mii: + gender_male: "Mężczyzna" + gender: "Płeć" + gender_female: "Kobieta" + vertical_position: "Pionowa Pozycja (Góra/Dół)" + size: "Rozmiar" + nose_type: "Rodzaj Nosa" + horizontal_position: "Pozioma Pozycja (Lewo/Prawo)" + mouth_type: "Rodzaj Ust" + hair_color: "Kolor Włosów" + hair_type: "Rodzaj Włosów" + mirror_hair: "Odwrócone Włosy" + glasses_type: "Rodzaj Okularów" + glasses_color: "Kolor Okularów" + name: "Imię Mii" + creator_name: "Imię Twórcy" + height: "Wysokość" + width: "Szerokość" + fav_color: "Ulubiony Kolor" + skin_color: "Kolor Skóry" + head_shape: "Kształt Głowy" + irish_color: "Kolor Oczu" + space_between: "Miejsce Pomiędzy" + rotation: "Obrót (Obróć Lewo/Prawo)" + eyebrow_type: "Rodzaj Brwi" + mustache_type: "Rodzaj Wąsa" + mustache_vertical_position: "Pionowa Pozycja Wąsa (Góra/Dół)" + mustache_size: "Rozmiar Wąsa" + beard_type: "Rodzaj Brody" + default_name: "Nowy Mii" + status: "Status" + priority: "Priorytet" + sort_by: "Sortuj Według" + total_games_won: "Ilość wygranych gier" + total_games_played: "Ilość zagranych gier" + vr_abbreviation: "OV*" + br_abbreviation: "OB" + br_full: "Ocena Bitewna" + vr_full: "Ocena Versus*" + description: "Opis" + images: "Obrazy" + likes: "Polubienia" + views: "Wyświetlenia" + downloads: "Pobrania" + mii_section: + general: "Ogólne" + face: "Twarz" + hair: "Włosy" + eyebrows: "Brwi" + eyes: "Oczy" + nose: "Nos" + lips: "Usta" + glasses: "Okulary" + facial_hair: "Zarost" + mole: "Pieprzyk" + gender: "Płeć" + gender_female: "Kobieta" + gender_male: "Mężczyzna" + state: + loading: "Ładowanie..." + installing: "Instalowanie..." + updating: "Aktualizowanie..." + config_not_finished: "Konfiguracja nie zakończona" + no_server: "Brak Serwera" + unknown: "Nieznany" + error: "Błąd" + warning: "Ostrzeżenie" + success: "Sukces" + extracting: "Wyodrębnianie plików..." + no_name: "Brak Nazwy" + no_license: "Brak Licencji" + no_profiles: "Brak Profili" + custom: "Niestandardowe" + installed: "Zainstalowane" + action: + play: "Graj" + play_offline: "Graj Offline" + install: "Instaluj" + update: "Aktualizuj" + import: "Importuj" + convert_to_patches: "Konwertuj na poprawki" + enable_all: "Włącz wszystkie" + disable_all: "Wyłącz wszystkie" + rename: "Zmień Nazwe" + delete: "Usuń" + open_folder: "Otwórz Folder" + edit: "Edytuj" + cancel: "Anuluj" + save: "Zapisz" + copy_friend_code: "Kopiuj Kod Przyjaciela" + export: "Eksportuj" + apply: "Zastosuj" + yes: "Tak" + no: "Nie" + keep: "Zostaw" + ok: "Okej" + revert: "Przywróć" + maybe_later: "Może Później" + browse: "Przeglądaj" + view_mod: "Zobacz Moda" + save_external_mii: "Dodaj Mii do \"Moje Mii\"" + view_mii: "Zobacz Mii" + duplicate: "Duplikuj" + back: "Wróć" + unfavorite: "Odulub" + favorite: "Ulubione" + view_room: "Zobacz Pokój" + link: + discord: "Pogadaj z nami!" + github: "Kod źródłowy" + support: "Wesprzyj nas!" + launch_dolphin: "Uruchom Dolphin" + randomize: "Losuj" + do_manually: "Wykonaj ręcznie" + gamebanana_page: "Strona Gamebanana" + report: "Zgłoś" + un_install: "Odinstaluj" + download_and_install: "Pobierz i Instaluj" + view_custom_chars: "Zobacz Niestandardowe Znaki" + submit: "Potwierdź" + region: + japan: "Japonia" + europe: "Europa" + china: "Chiny" + south_korea: "Korea Południowa" + taiwan: "Tajwan" + america: "Ameryka" + australia: "Australia" + list_title: + friends: "Przyjaciele" + players: "Gracze" + rooms: "Pokoje" + miis: "Mii" + popup_title: + mod_details: "Szczegóły Moda" + mod_browser: "Przeglądacz Modów" + mii_selector: "Selektor Mii" + mii_editor: "Edytor Mii" + text: + made_by_string: |- + Stworzone przez: {$1} + i {$2} + thanks_translators: "Dziękujemy bardzo wszystkim tłumaczą:" + wh_wz_translation_percentage: "Tłumaczenie dla tego języka jest skończone w {$1}%" + powered_gamebanana: "Wsparci Przez Gamebanana" + language_translated_by: "Ten język jest przetłumaczony przez: {$1}" + hover: + players_online: + 0: "Nie ma żadnych graczy online" + 1: "Jest 1 gracz online" + n: "jest teraz {$1} graczy online" + rooms_online: + 0: "Nie ma żadnych aktywnych pokoi" + 1: "Jest 1 aktywny pokój" + n: "jest teraz {$1} aktywnych pokoi" + friends_online: + 0: "żaden z twoich przyjaciół nie jest onllne" + 1: "Jest teraz 1 przyjaciel online" + n: "Jest teraz {$1} przyjaciół online" + rooms_page_disclaimer: "Prosze pamiętaj, że ta strona nie daje możliwości dołączenia prosto do pokoju. By dołączyć do specyficznego pokoju musisz dołączyć poprzez przyjaciela lub mieć nadzieje, że uda ci się połączyć poprzez losowy serwer." + friends_page_disclaimer: "By dodać przyjaciół musisz ich dodać w grze" + region_user_selection: "To tylko pokazuje regiony, w których TY grałeś." + empty_content: + no_profiles: "Musisz zagrać w gre przynajmniej raz by zobaczyć tu swoje profle" + no_friends_title: "Brak przyjaciół!" + no_friends: "Możesz dodać przyjaciół w grze" + no_mods: "Mody mogą zmienić jak gra działa. Zacznij importować swój pierwszy mod klikając przycisk poniżej." + no_mods_title: "Nie znaleziono modów" + no_rooms: "Możesz nie mieć połączenia z internetem lub nikt teraz nie gra." + no_rooms_title: "Nie znaleziono pokoi" + no_miis_title: "Brak Mii!" + no_miis: "Nie możemy odczytać danych Mii z twojego systemu. Upewnij sie, że uruchomiłeś gre przynajmniej raz" + no_mod_selected_title: "Nie wybrano moda" + no_mod_selected: "Wybierz moda na liście by zobaczyć jego szczegóły" + time: + days: + 1: "1 dzień" + n: "{$1} dni" + hours: + 1: "1 godz" + n: "{$1} godz" + minutes: + 1: "1 min" + n: "{$1} min" + seconds: + 1: "1 sek" + n: "{$1} sek" + progress: + estimated_time_remaining: "Oszacowany pozostały czas." + downloading_mb: "Pobieranie {$1} MB" + installing_mods: "Instalowanie modów" + installing_mods_count: "Instalowanie {$1} modów" + processing_xof_y: "Procesowanie {$1} z {$2} Plików" + update_rr: "Aktualizowanie Retro Rewind" + update_wh_wz: "Aktualizowanie Wheel Wizard" + installing_rr: "Instalowanie Retro Rewind" + installing_rr_first_time: "Instalowanie Retro Rewind pierwszy raz" + installing_dolphin: "Instalowanie Emulatora Dolphin" + this_may_take_a_while: "Może to zająć chwile zależnie od twojego połączenia z internetem." + applying_converted_mod: "Zastosowywanie skonwertowanych modów" + combining_files: "Łączenie plików" + converting_file: "Konwertowanie {$1}" + converting_files_count: "Konwertowanie {$1} Pliku(ów)" + converting_mod_to_patches: "Konwertowanie modów na poprawki" + downloading_mod: "Pobieranie {$1}" + installing_mod: "Instalowanie Moda" + preparing_files_count: "Przygotowywanie {$1} Pliku(ów)" + question: + launch_clear_mods_found: + title: "Znaleziono Mody" + extra: "Zaraz uruchomisz grę bez żadnych modów. Czy chcesz wyczyścić twój my-stuff folder?" + sure_delete: + title: "Jesteś pewien, że chcesz usunąć {$1}?" + extra: "Usuwanie jest nie odwracalne." + title_miis: "Jesteś pewien, że chcesz usunąć {$1} Mii?" + apply_scale: + title: "Czy chcesz zastosować nową skalę?" + extra: "Ta zmiana zostanie cofnięta za {$1} może, że postanowisz ją zastosować." + move_data: + title: "Przenieść dane z Wheel Wizard?" + extra: |- + Wheel Wizard przeniesie swoje pliki do: + {$1} + to może zająć chwile zależnie od ilości danych. + dolphin_found: + title: "Znaleziono folder Emulatora Dolphin" + extra: |- + Jeżeli nie wiesz co to wszystko znaczy poprostu naciśnij tak :) + Znaleziono folder Emulatora Dolphin. Chciałbyś użyć ten folder? + new_version_wh_wz: + title: "Aktualizacja do Wheel Wizard dostępna" + extra: "Wersja {$1} Wheel Wizard jest dostępna (obecnie na {$2}). Chcesz aktualizować teraz?" + update_admin: + title: "Aktualizuj używając uprawnień administratora" + extra: "Czasami aktualizacja wymaga uprawnień administratora czy chcesz je aktywować dla tej aktualizacji?" + reinstall_rr: + title: "Zainstaluj Retro Rewind ponownie" + extra: "Jesteś pewien, że chcesz zainstalować Retro Rewind ponownie?" + old_rksys_found: + title: "Znaleziono stary plik rksys.dat" + extra: "Znaleziono stary zapis. Chciałbyś go użyć? (Zalecane)" + rr_to_old: + extra: "Twoja wersja Retro Rewind jest zbyt stara by ją aktualizować. Chcesz zainstalować Retro Rewind ponownie?" + title: "Wersja Retro Rewind jest zbyt stara." + install_mod: + title: "Czy chcesz pobrać i zainstalować mod: {$1}?" + search_mod: "Szukaj modów..." + enter_new_name: + title: "Wpisz nowe imię" + extra: "Zmienianie imienia z: {$1}" + enter_mod_name: + title: "Nazwa Moda:" + launch_clear_patches_found: + extra: "Zaraz uruchomisz grę bez modów. Czy chcesz wyczyścić swój folder poprawek? Uruchamianie gry tak czy siak uruchomi ją z aktualnym folderem poprawek dalej aktywnym!" + apply_language_settings: + title: "To zamknie obecne okno i otworzy nowe z nowymi ustawieniami językowymi." + extra: "Czy chcesz zastosować nowe ustawienia językowe?" + message_warning: + not_find_dolphin: + extra: "Nie można było znaleść Emulatora Dolphin prosze ustaw ścieżkę w ustawieniach" + not_find_game: + extra: "Nie można było znaleść gry prosze ustaw ścieżkę w ustawieniach" + multiple_files_selected: + title: "Wybrano Wiele Plików" + invalid_name: + extra: + mod_name_exists: "Mod z nazwą \"{$1}\" już istnieje" + title: "Nazwa niedozwolona" + invalid_paths: + extra: "Proszę się upewnij, że wszystkie ścieżki są poprawnę i spróbuj ponownie." + title: "Niepoprawne ścieżki" + dolphin_not_found: + title: "Nie znaleziono folderu Emulatora Dolphin" + extra: "Folder Emulatora Dolphin nie został znaleziony automatycznie. Proszę spróbuj znaleść folder manualnie kliknij \"pomoc\" po więcej informacji" + dolphin_tool_selected: + title: "Wybrano zły plik wykonywalny Dolphina" + extra: "Wybrałeś DolphinTool.exe. Proszę wybierz Dolphin.exe (Plik wykonywalny Emulatora Dolphin) zamiast tego" + fail_check_updates: + title: "Nie udało się sprawdzić dostępności aktualizacji" + extra: "Nie udało się sprawdzić dostępności aktualizacji w tej chwili. Możesz nie być podłączony do internetu lub serwer może teraz nie działać." + unable_update_wh_wz: + extra: + reason_network: |- + Nie można sprawdzić czy Wheel Wizard jest na najnowszej wersji. + Możesz mieć problem z internetem. + reason_location: |- + Nie można zaktualizować Wheel Wizard. Proszę upewnij się, że aplikacja jest zlokalizowana w folderze, który ma możliwość zapisu. + Nie można znaleść obecnego folderu. + title: "Nie można zaktualizować Wheel Wizard." + no_connect_server: + extra: "Nie można połączyć się ze serwerem. Spróbuj ponownie później." + title: "Nie można połączyć się ze serwerem" + cant_view_mod: + title: "Nie można zobaczyć tego moda." + extra: + something_else: "Coś poszło nie tak próbując otworzyć wybrany mod." + not_from_browser: "Nie można zobaczyć moda, który nie był zainstalowany poprzez Przeglądacza Modów." + cannot_delete_fav_mii: + title: "Nie można usunąć ulubionych Mii" + extra: "Jeden lub więcej z zaznaczonych Mii jest ulubiony. Mii mogą być tylko usunięte jeżeli nie są z listy ulubionych by zapobiec przed przypadkowym usunięciem." + mod_name_invalid: + title: "Nieprawidłowa nazwa moda." + extra: "Nazwa moda zawiera nieprawidłowe znaki." + mod_name_empty: + title: "Nazwa moda nie może być pusta." + extra: "Proszę podaj nazwe moda." + unable_download_mod: + title: "Nie można pobrać moda" + extra: "Nie można znaleść plików." + could_not_convert_mod: + title: "Nie można skonwertować moda" + placeholder: + enter_mod_name: "Wpisz nazwe moda..." + search_mod: "Szukaj modów..." + enter_mii_name: "Wpisz imię Mii..." + enter_text_here: "Wpisz tutaj tekst..." + search_for_players: "Szukaj Graczy..." + enter_path: "Podaj chcianą ścieżkę tutaj..." + message_error: + no_mod_folder: + extra: "Folder modów nie istnieje" + data_folder_move: + title: "Nie udało się przenieść danych" + abort_rr: + extra: + failed_update_delete: "Nie udało się usunąć plików dla aktualizacji. Abortowanie." + failed_update_apply: "Nie udało się zastosować aktualizacji. Abortowanie" + invalid_file_path: |- + Wykryto nieprawidłową ścieżkę do pliku. Proszę skontaktuj się z deweloperami. + Błąd Serwera: {$1} + title: "Abortowanie aktualizacji Retro Rewind" + restart_admin_fail: + extra: "Nie udało się zrestartować z uprawnieniami administracyjnymi." + generic_error: + title: "Wystąpił błąd." + failed_create_mii_db: + title: "Nie udało się stworzyć bazy danych dla Mii" + failed_change_name: + title: "Nie udało się zmienić imienia." + failed_change_mii: + title: "Nie udało się zmienić Mii" + failed_install_dolphin: + title: "Nie udało się zainstalować Dolphina" + extra: "Instalacja Emulatora Dolphin się nie powiodła. Proszę spróbuj manualnie zainstalować Dolphina poprzez Flatpak." + failed_retrieve_mod: + title: "Nie udało się odzyskać informacji moda" + mod_download_fail: + title: "Nie udało się pobrać moda." + extra: "Wystąpił błąd podczas pobierania: {$1}" + message_success: + settings_saved: + title: "Ustawienia zapisane pomyślnie!" + data_folder_moved: + title: "Zaktualizowano folder danych" + extra: |- + Dane Wheel Wizard są teraz przechowywane w: + {$1} + rr_up_to_date: + title: "Retro Rewind jest na najnowszej wersji." + mii_changed: "Zmieniono Mii pomyślnie" + mod_converted_to_patches: + title: "Konwertuj na poprawki" + mod_installed: + extra: "Mod \"{$1}\" zainstalowany pomyślnie." + title: "Zainstalowano moda pomyślnie!" + patch_conversion_skipped: "Zostawiono {$1} plik(i/ów) niezmienionych bo nie mogły być skonwertowane." + snackbar_warning: + cant_save_mii: "Nie można zapisać Mii" + no_mii_export: "Wygląda na to, że nie było żadnych Mii do eksportowania" + no_mii_delete: "Wygląda na to, że nie było żadnych Mii do usunięcia" + snackbar_success: + mii_added: "Mii został dodany do twoich Mii" + copied_fc: "Skopiowano kod przyjaciela do schowka" + name_change: "Pomyślnie zmieniono imię na \"{$1}\"" + saved_mii: "Zapisano Mii \"{$1}\" do pliku \"{$2}\"" + deleted: "Usunięto \"{$1}\"" + deleted_miis: "Usunięto {$1} Mii" + created_duplicate: "Stworzono kopie \"{$1}\"" + created_duplicates_miis: "Stworzono {$1} kopi Mii" + profile_set_primary: "Ustaw jako główny profil" + snackbar_error: + mii_failure_update: "Nie udało się zaktualizować Mii \"{$1}\"" + mii_failure_save: "Nie udało się zapisać Mii \"{$1}\"" + mii_failure_get: "Nie udało się zdobyć Mii \"{$1}\"" + mii_failure_create: "Nie udało się stworzyć Mii \"{$1}\"" + mii_failure_duplicate: "Nie udało się skopiować Mii \"{$1}\"" + helper_note: + name_must_between: "Nazwy muszą mieć od 3 do 10 znaków." + creator_name_less11: "Nazwa twórcy musi mieć mniej niż 11 znaków." + creator_name_less10: "Nazwa twórcy musi mieć mniej niż 10 znaków." + file_picker: + select_mod_file: "Wybierz Plik Moda" + patch: + incompatible_mod: + message: "Ten mod jest niekompatybilny z innymi modami. Uruchamianie gry z tym modem może spowodować niepoprawne zachowanie albo nagłe wyłączenie gry. Rozważ skonwertowanie go na poprawki poprzez klikanie na moda prawym przyciskiem myszy." + title: "MOD NIEKOMPATYBILNY" + option: + wh_wz_language: "Język Wheel Wizard" + force_disable_wiimote: "Zmuś Wyłączenie Wiimota" + launch_with_dolphin: "Uruchom grę razem z oknem Dolphina" + launch_rr_on_startup: "Uruchom Retro Rewind razem z uruchomieniem Wheel Wizard." + v_sync: "VSync" + recommended: "Polecane ustawienia" + show_fps: "Pokaż FPS" + window_scale: "Skala Okna" + open_config: "Otwórz folder konfiguracji" + wheel_wizard_data_folder: "Folder danych Wheel Wizard" + reset_data_folder: "Użyj domyślnej lokacji" + dolphin_user_path: "Folder Użytkownika Dolphina" + mario_kart_game: "Plik gry Mario Kart Wii" + open_game_folder: "Otwórz Folder Gry" + open_save_folder: "Otwórz Folder Zapisów" + section: + graphics: "Grafika" + general: "Ogólne" + language: "Język" + wii: "Ustawienia Wii" + resolution: "Rezolucja" + performance: "Ustawienia Wydajności" + location_paths: "Ustawienia Lokacji" + wh_wz_appearance: "Wygląd Wheel Wizard" + installation: "Instalacja" + category: + other: "Inne" + video: "Wideo" + value: + language: + france: "Francuski" + dutch: "Holenderski" + english: "Angielski" + german: "Niemiecki" + italian: "Włoski" + japanese: "Japoński" + turkish: "Turecki" + spanish: "Hiszpański" + finnish: "Fiński" + korean: "Koreański" + russian: "Rosyjski" + arabic: "Arabski" + czech: "Czeski" + norwegian: "Norweski" + chinese: "Chinski" + polish: "Polski" + portuguese: "Portugalski" + z_translators: "tim13." diff --git a/WheelWizard/Resources/Languages/port_script.py b/WheelWizard/Resources/Languages/port_script.py index 08598e616..da214fdb1 100644 --- a/WheelWizard/Resources/Languages/port_script.py +++ b/WheelWizard/Resources/Languages/port_script.py @@ -2,6 +2,7 @@ import csv import datetime as dt +import re import sys from pathlib import Path @@ -9,7 +10,7 @@ LANGUAGE_DIR = Path(__file__).resolve().parent TRANSLATOR_KEY = "value.language.z_translators" TRANSLATOR_EXPORT_HINT = "Put your name here if you contributed." -NEWLINE_NORMALIZED_IMPORT_KEYS = {"hover.rooms_page_disclaimer"} +TRANSLATION_KEY_PATTERN = re.compile(r"^[a-z0-9_]+(?:\.[a-z0-9_]+)*$") def parse_yaml_file(path: Path) -> tuple[str, dict]: @@ -46,7 +47,7 @@ def parse_yaml_file(path: Path) -> tuple[str, dict]: root_code = key continue - if raw_value == "|-": + if raw_value in {"|", "|-", "|+"}: block_lines: list[str] = [] block_indent = indent + 2 while index < len(lines): @@ -61,7 +62,7 @@ def parse_yaml_file(path: Path) -> tuple[str, dict]: else: block_lines.append("") - current[key] = "\n".join(block_lines) + current[key] = normalize_newlines("\n".join(block_lines)) continue current[key] = unquote_yaml_value(raw_value) @@ -91,7 +92,7 @@ def unquote_yaml_value(value: str) -> str: result.append(char) index += 1 - return "".join(result) + return normalize_newlines("".join(result)) def write_yaml_file(path: Path, language_code: str, values: dict) -> None: @@ -112,7 +113,7 @@ def append_yaml_dict(lines: list[str], values: dict, indent: int) -> None: if "\n" in text or "\r" in text: lines.append(f"{prefix}{key}: |-") for block_line in text.replace("\r\n", "\n").replace("\r", "\n").split("\n"): - lines.append(" " * (indent + 2) + block_line) + lines.append(" " * (indent + 2) + block_line if block_line else "") continue lines.append(f'{prefix}{key}: "{quote_yaml_value(text)}"') @@ -179,6 +180,8 @@ def ordered_translation_keys(languages: dict[str, dict[str, str]]) -> list[str]: for key in languages[language_code]: if key in seen: continue + if not TRANSLATION_KEY_PATTERN.fullmatch(key): + continue seen.add(key) keys.append(key) @@ -203,7 +206,7 @@ def export_csv() -> Path: if code == "en" and key == TRANSLATOR_KEY: row_values.append(TRANSLATOR_EXPORT_HINT) else: - row_values.append(languages[code].get(key, "")) + row_values.append(normalize_export_value(languages[code].get(key, ""))) writer.writerow([key, *row_values]) @@ -322,10 +325,23 @@ def ask_for_csv_path() -> Path | None: def normalize_import_value(key: str, value: str) -> str: - if key not in NEWLINE_NORMALIZED_IMPORT_KEYS: - return value + return normalize_newlines(value) + + +def normalize_export_value(value: str) -> str: + normalized = value.replace("\r\n", "\n").replace("\r", "\n") + return normalized.replace("\n", "\\n") + + +def normalize_newlines(value: str) -> str: + normalized = value.replace("\\r\\n", "\n").replace("\\n", "\n") + return "\n".join(line.strip() for line in normalized.split("\n")) - return value.replace("\\r\\n", "\n").replace("\\n", "\n") + +def normalize_yaml_files() -> None: + for path in sorted(LANGUAGE_DIR.glob("*.yml")): + language_code, values = parse_yaml_file(path) + write_yaml_file(path, language_code, values) def ask_mode() -> str: @@ -350,6 +366,11 @@ def main() -> int: import_csv() return 0 + if mode in {"n", "normalize"}: + normalize_yaml_files() + print("Normalized translation YAML files") + return 0 + print("Nothing selected.") return 1 except Exception as ex: diff --git a/WheelWizard/Resources/Languages/pt.yml b/WheelWizard/Resources/Languages/pt.yml index 6cd134178..e9b4fbc79 100644 --- a/WheelWizard/Resources/Languages/pt.yml +++ b/WheelWizard/Resources/Languages/pt.yml @@ -3,15 +3,18 @@ pt: home: "Casa" settings: "Definições" mods: "My stuff" + patches: "Patches" my_profiles: "Os meus perfis" rooms: "Salas" friends: "Amigos" + leaderboard: "Tabela de Ranking" room_details: "Detalhes da sala" my_miis: "Meus Miis" attribute: status_online: "Online" enabled: "Ativado" title: "Título" + all: "Tudo" status_offline: "Offline" name: "Nome" id: "ID" @@ -32,7 +35,7 @@ pt: gender: "Género" gender_female: "Mulher" vertical_position: "Posição Vertical (Cima/Baixo)" - size: "Tamanho " + size: "Tamanho" nose_type: "Tipo de Nariz" horizontal_position: "Posição Horizontal (Esquerda/Direita)" mouth_type: "Tipo de Boca" @@ -62,8 +65,8 @@ pt: priority: "Prioridade" sort_by: "Organizar por" total_games_won: "Total de jogos ganhos" - total_games_played: "Total de jogos jogados " - vr_abbreviation: "CV " + total_games_played: "Total de jogos jogados" + vr_abbreviation: "CV" br_abbreviation: "CB Médio" br_full: "Classificação de Batalha" vr_full: "Classificação Versus" @@ -108,6 +111,7 @@ pt: install: "Instalar" update: "Atualizar" import: "Importar" + convert_to_patches: "Converter para patches" enable_all: "Ativar tudo" disable_all: "Desativar tudo" rename: "Mudar o nome" @@ -149,7 +153,7 @@ pt: submit: "Submeter" region: japan: "Japão" - europe: "Europa " + europe: "Europa" china: "China" south_korea: "Coreia do Sul" taiwan: "Taiwan" @@ -167,13 +171,20 @@ pt: mii_editor: "Editor Mii" mii_carousel: "Carrosel Mii" text: - made_by_string: | + made_by_string: |- Feito por: {$1} e {$2} thanks_translators: "Muito obrigado a todos os tradutores:" wh_wz_translation_percentage: "Traduções para esta língua estão {$1}% completas" powered_gamebanana: "Contribuido pelo GameBanana" language_translated_by: "Esta língua é traduzida por: {$1}" + brsar_entry: "{$1} entrada {$2}" + brsar_entry_with_rwar: "{$1} entrada {$2} + RWAR" + external_reference: "Referência externa" + external_reference_with_path: "Referência externa: {$1}" + modified_archive_member: "Item do arquivo modificado" + new_archive_member: "Novo item do arquivo modificado" + whole_file_override: "Substituição do arquivo inteiro" hover: players_online: 0: "Não há jogadores online" @@ -212,7 +223,7 @@ pt: n: "{$1} horas" minutes: 1: "1 minuto" - n: "{$1} minutos " + n: "{$1} minutos" seconds: 1: "1 segundo" n: "{$1} segundos" @@ -229,22 +240,35 @@ pt: installing_rr_first_time: "Instalando Retro Rewind pela primeira vez" installing_dolphin: "A instalar o Dolphin Emulator" this_may_take_a_while: "Isto pode demorar um pouco, dependendo na sua conexão de internet." + applying_converted_mod: "A aplicar o mod convertido" + combining_files: "Combinando ficheiros" + converting_file: "A converter {$1}" + converting_files_count: "A converter {$1} ficheiro(s)" + converting_mod_to_patches: "A converter o mod para patches" + downloading_mod: "A descarregar {$1}" + installing_mod: "A instalar o mod" + preparing_files_count: "A preparar {$1} ficheiro(s)" question: launch_clear_mods_found: - title: "Mods não encontrados" + title: "Mods encontrados" extra: "Tu estás prestes a abrir o jogo sem nenhum mod. Queres limpar a pasta my-stuff?" sure_delete: - title: "Tens a certeza que desejas apagar ($1)?" + title: "Tens a certeza que desejas apagar {$1}?" extra: "A eliminação é permanente e não pode ser revertida." title_miis: "Tem a certeza de que deseja apagar {$1} Miis?" apply_scale: title: "Gostarias de aplicar a nova escala?" extra: "Esta alteração será revertida em {$1} a menos que decida manter a alteração." + move_data: + title: "Mover a data do Wheel Wizard?" + extra: |- + O Wheel Wizard vai mover os seus ficheiros para : + {$1} + Pode demorar bastante tempo, dependendo da quantidade de dados. dolphin_found: title: "Pasta Do Dolphin Emulator Encontrada" - extra: | + extra: |- Se não sabes o que isto significa, clica só que sim :) - Pasta do Dolphin Emulator encontrada. Gostarias de usar esta pasta? new_version_wh_wz: title: "Atualização do Wheel Wizard Disponível" @@ -273,6 +297,13 @@ pt: enter_new_name: title: "Introduza um novo nome" extra: "Alterando o nome de: {$1}" + enter_mod_name: + title: "Nome do mod:" + launch_clear_patches_found: + extra: "Está prestes a iniciar o jogo sem mods. Pretende limpar a pasta de Patches? Iniciar o jogo assim vai iniciá-lo com a atual pasta de patches ainda ativa!" + apply_language_settings: + title: "Isto vai fechar a janela atual e abrir uma nova com as novas definições de linguagem." + extra: "Pretende aplicar as novas definições de linguagem?" message_warning: not_find_dolphin: extra: "O Dolphin Emulator não foi encontrado, por favor defina o caminho nas definições" @@ -288,21 +319,22 @@ pt: extra: "Por favor verifica que todos os percursos estão corretos e tenta outra vez." title: "Caminhos inválidos." dolphin_not_found: - title: "Pasta Do Emulador Dolphin Não Encontrada " + title: "Pasta Do Emulador Dolphin Não Encontrada" extra: "A pasta Do Emulador Dolphin não foi automáticamente encontrada. Por favor tente encontrar a pasta manualmente, clica em 'ajuda' para obter mais informação-" + dolphin_tool_selected: + title: "Executável incorreto do Dolphin selecionado." + extra: "Selecionou DolphinTool.exe. Por favor escolha Dolphin.exe (o executável do Dolphin Emulator)." fail_check_updates: title: "Falha ao procurar atualizações" extra: "Não é possível verificar se há atualizações no momento. Pode não estar conectado à internet ou o servidor pode estar indisponível ou em baixo." unable_update_wh_wz: extra: - reason_network: | + reason_network: |- Impossível verificar se o Wheel Wizard está atualizado. - - Talvez estejas a experienciar problemas de net. - reason_location: | + Talvez estejas a experienciar problemas de net + reason_location: |- Incapaz de atualizar o Wheel Wizard. Garante que a aplicação se encontra numa pasta que pode ser localizada. - - Falha a encontar a pasta atual. + Falha a encontar a pasta atual title: "Não é possível atualizar o Wheel Wizard" no_connect_server: extra: "Falha ao conectar ao servidor. Tente de novo mais tarde" @@ -324,6 +356,8 @@ pt: unable_download_mod: title: "Não é possível descarregar o mod" extra: "Os ficheiros não foram encontrados." + could_not_convert_mod: + title: "Não foi possível converter o mod." placeholder: enter_mod_name: "Introduzir nome do mod..." search_mod: "Pesquisa por mods..." @@ -334,13 +368,14 @@ pt: message_error: no_mod_folder: extra: "A pasta do mod não existe" + data_folder_move: + title: "Não foi possível mover os dados" abort_rr: extra: failed_update_delete: "Falha a apagar ficheiros para a atualização. Abortando." failed_update_apply: "Falha a aplicar a atualização. Abortando." - invalid_file_path: | + invalid_file_path: |- Caminho de arquivo inválido detectado. Entra em contato com os desenvolvedores. - Erro do servidor: {$1} title: "A abortar o Update do RR." restart_admin_fail: @@ -360,17 +395,30 @@ pt: title: "Falha ao obter informações do mod" mod_download_fail: title: "O download do mod falhou." - extra: "Ocorreu um erro durante o download: {$1} " + extra: "Ocorreu um erro durante o download: {$1}" message_success: settings_saved: title: "Definições guardadas com sucesso" + data_folder_moved: + title: "Pasta de dados atualizada." + extra: |- + Dados do Wheel Wizard são agora guardados em: + {$1} rr_up_to_date: title: "A tua versão do Retro Rewind é a mais recente" mii_changed: "Mii mudado com sucesso" + mod_converted_to_patches: + title: "Convertido para patches" + mod_installed: + extra: "Mod '{$1}' instalado com sucesso." + title: "Mod instalado com sucesso!" + patch_conversion_result: "{$1} Ficheiro(s) convertidos para {$2} ficheiro(s) de patch." + patch_conversion_skipped: "{$1} Ficheiro(s) inalterados pois não era possível serem convertidos." + patch_conversion_notes: "Nota(s): {$1}" snackbar_warning: cant_save_mii: "Não é possível guardar o Mii" no_mii_export: "Parece que não havia Miis para exportar" - no_mii_delete: "Parece que não havia Miis para apagar " + no_mii_delete: "Parece que não havia Miis para apagar" snackbar_success: mii_added: "Mii foi adicionado aos teus Miis" copied_fc: "Código de amigo copiado para a área de transferência" @@ -378,7 +426,7 @@ pt: saved_mii: "Mii '{$1}' salvado para o ficheiro '{$2}'" deleted: "Apagado '{$1}'" deleted_miis: "{$1} Miis apagados" - created_duplicate: "Duplica de '{$1}' criada " + created_duplicate: "Duplica de '{$1}' criada" created_duplicates_miis: "Criados '{$1}' Miis duplicados" profile_set_primary: "Definir perfil como principal" snackbar_error: @@ -406,12 +454,13 @@ pt: mario_kart_game: "Ficheiro de Jogo do Mario Kart Wii" dolphin_emulator_exe: "Executável do Dolphin Emulator" make_primary: "Tornar Primário" - remove_blur: "Remover Blur" enable_animations: "Ativar Animações" reinstall: "Reinstalar RR" open_game_folder: "Abrir a Pasta do Jogo" open_save_folder: "Abrir a Pasta de Ficheiros de Gravação" section: + graphics: "Gráficos" + general: "Geral" language: "Linguagem" wii: "Definições da Wii" resolution: "Resolução" @@ -419,7 +468,7 @@ pt: renderer: "Definições de Renderização" location_paths: "Definições da Localização" wh_wz_appearance: "Aparência do Wheel Wizard" - installation: "Instalação " + installation: "Instalação" helper_text: topbar: location_warning: "As definições estão desativadas, completa as definições de localização primeiro" @@ -431,14 +480,16 @@ pt: end_with_exe: "O caminho deve acabar com .exe" enable_animations: "Isto ativa as animações na app Wheel Wizard" end_with_x: "O caminho pode acabar com:" - wh_wz_language: "Isto só muda o idioma do Wheel Wizard. Para mudar o idioma dentro do jogo, precisas de fazer isso nas configurações do próprio jogo." optional: "Opcional" must_set_paths: "Precisas de configurar estes 3 caminhos antes de começar a jogar Retro Rewind." category: - video: "Video " - about: "Acerca de " + about: "Acerca de" other: "Outro" general: "Geral" + video: "Video" + status: + data_folder: + moving: "A mover dados, por favor aguarde..." value: language: france: "Francês" @@ -457,4 +508,4 @@ pt: norwegian: "Norueguês" chinese: "Chinês" portuguese: "Português" - z_translators: "JMM, Gui_C__Tuga" + z_translators: "JMM, Gui_C__Tuga, bubble" diff --git a/WheelWizard/Resources/Languages/ru.yml b/WheelWizard/Resources/Languages/ru.yml index 20a8faffa..162c2b3b2 100644 --- a/WheelWizard/Resources/Languages/ru.yml +++ b/WheelWizard/Resources/Languages/ru.yml @@ -167,7 +167,7 @@ ru: mii_editor: "Редактор Mii" mii_carousel: "Карусель Mii" text: - made_by_string: | + made_by_string: |- Создатели: {$1} и {$2} thanks_translators: "Низкий поклон и спасибо переводчикам:" @@ -217,7 +217,7 @@ ru: estimated_time_remaining: "Осталось примерно:" downloading_mb: "Загружено {$1} МБ" installing_mods: "Устанавливаем моды" - installing_mods_count: "Устанавливаем {$!} модов" + installing_mods_count: "Устанавливаем {$1} модов" processing_xof_y: "Обработка {$1} из {$2} файлов..." update_rr: "Обновляем Retro Rewind" latest_wh_wz_github: "Получаем последнюю версию Wheel Wizard из GitHub" @@ -237,9 +237,8 @@ ru: title: "Хотите применить новый масштаб?" dolphin_found: title: "Папка эмулятора Dolphin найдена" - extra: | + extra: |- Если ты понятия не имеешь что это значит, просто нажми да :D - Папка эмулятора Dolphin найдена. Хотите её использовать? new_version_wh_wz: title: "Доступно обновление Wheel Wizard" @@ -283,13 +282,11 @@ ru: title: "Не удалось проверить обновления" unable_update_wh_wz: extra: - reason_network: | + reason_network: |- Не удалось проверить, обновлён ли Wheel Wizard до последней версии? - Вы возможно испытываете проблемы с подключеним. - reason_location: | + reason_location: |- Не удалось обновить Wheel Wizard. Пожалуйста убедитесь, что исполняемый файл находится в папке куда можно добавлять файлы. - Не удалось найти текущую папку. title: "Не удалось обновить Wheel Wizard." no_connect_server: @@ -365,12 +362,13 @@ ru: mario_kart_game: "Файл Mario Kart Wii" dolphin_emulator_exe: "Исполняемый файл (.exe) эмулятора Dolphin" make_primary: "Сделать основным" - remove_blur: "Отключить блюр" enable_animations: "Включить анимации" reinstall: "Переустановить Retro Rewind" open_game_folder: "Открыть папку с игрой" open_save_folder: "Открыть папку сохранения" section: + graphics: "Графика" + general: "Общие" language: "Язык" wii: "Настройки Wii" resolution: "Разрешение" @@ -391,10 +389,10 @@ ru: enable_animations: "Эта настройка включает анимации в лаунчере Wheel Wizard." end_with_x: "Путь может заканчиваться на:" category: - video: "Видео" about: "О нас" other: "Другое" general: "Основное" + video: "Видео" value: language: france: "Французский" diff --git a/WheelWizard/Resources/Languages/tr.yml b/WheelWizard/Resources/Languages/tr.yml index 6d13e2f6a..c20ed329b 100644 --- a/WheelWizard/Resources/Languages/tr.yml +++ b/WheelWizard/Resources/Languages/tr.yml @@ -167,9 +167,9 @@ tr: mii_editor: "Mii Düzenleyicisi" mii_carousel: "Mii Görüntüleyicisi" text: - made_by_string: | + made_by_string: |- Yapımcı: {$1} - ve {$2} + Ve {$2} thanks_translators: "Tüm çevirmenlere çok teşekkür ederiz:" wh_wz_translation_percentage: "Bu dilin çevirileri {$1}% tamamlandı" powered_gamebanana: "GameBanana tarafından" @@ -244,9 +244,8 @@ tr: extra: "Bu değişiklik onaylamadığınız takdirde {$1} içinde eski haline dönecek." dolphin_found: title: "Dolphin Emulator Klasörü Bulundu" - extra: | + extra: |- Eğer bunların ne anlama geldiğini bilmiyorsanız, evet diyebilirsiniz :) - Dolphin Emulator klasörü bulundu. Bu klasörü kullanmak ister misiniz? new_version_wh_wz: title: "Wheel Wizard Güncellemesi Mevcut" @@ -297,13 +296,11 @@ tr: extra: "Şu an güncellemeler kontrol edilemiyor. İnternete bağlı olmayabilirsiniz veya sunucular çalışmıyor olabilir." unable_update_wh_wz: extra: - reason_network: | + reason_network: |- Wheel Wizard'ın güncel olup olmadığını kontrol edilemedi. - Ağ sorunları yaşıyor olabilirsiniz. - reason_location: | + reason_location: |- Wheel Wizard güncellenemedi. Uygulamanın erişilebilir bir klasörde bulunduğundan emin olun. - İstenilen klasör bulunamadı. title: "Wheel Wizard güncellenemedi." no_connect_server: @@ -340,9 +337,8 @@ tr: extra: failed_update_delete: "Güncelleme dosyalarını silmek başarısız oldu. İşlem iptal ediliyor." failed_update_apply: "Güncellemeyi uygulamak başarısız oldu. İşlem iptal ediliyor." - invalid_file_path: | + invalid_file_path: |- Yanlış dosya yolu tespit edildi. Lütfen geliştiricilere bildirin. - Sunucu hatası: {$1} title: "Retro Rewind güncellemesi iptal ediliyor" restart_admin_fail: @@ -409,12 +405,13 @@ tr: mario_kart_game: "Mario Kart Wii Oyun Dosyası" dolphin_emulator_exe: "Dolphin Emulator yürütülebilir dosyası" make_primary: "Birincil Yap" - remove_blur: "Bulanıklığı Kaldır" enable_animations: "Animasyonları Etkinleştir" reinstall: "Retro Rewind'ı Tekrar Kur" open_game_folder: "Oyun Klasörünü Aç" open_save_folder: "Kayıt Klasörünü Aç" section: + graphics: "Grafik" + general: "Genel" language: "Dil" wii: "Wii Ayarları" resolution: "Çözünürlük" @@ -434,14 +431,13 @@ tr: end_with_exe: "Yol, .exe ile bitmelidir." enable_animations: "Bu, Wheel Wizard uygulamasındaki animasyonları etkinleştirir." end_with_x: "Yol, bunlar ile bitebilir:" - wh_wz_language: "Bu sadece Wheel Wizard'ın dilini değiştirir. Oyun içi dili değiştirmek için oyundaki ayarları kullanın." optional: "Opsiyonel" must_set_paths: "Retro Rewind'ı oynayabilmek için önce bu üç yolu ayarlamanız gerekiyor" category: - video: "Video" about: "Hakkında" other: "Diğer" general: "Genel" + video: "Video" value: language: france: "Fransızca" diff --git a/WheelWizard/Shared/DevelopmentMode.cs b/WheelWizard/Shared/DevelopmentMode.cs new file mode 100644 index 000000000..c850863e9 --- /dev/null +++ b/WheelWizard/Shared/DevelopmentMode.cs @@ -0,0 +1,14 @@ +namespace WheelWizard.Shared; + +public static class DevelopmentMode +{ +#if DEBUG + public static bool IsEnabled { get; private set; } = true; + + public static void Hide() => IsEnabled = false; +#else + public static bool IsEnabled => false; + + public static void Hide() { } +#endif +} diff --git a/WheelWizard/Shared/MessageTranslations/MessageTranslationHelper.cs b/WheelWizard/Shared/MessageTranslations/MessageTranslationHelper.cs index ffd724e8a..b604a87fc 100644 --- a/WheelWizard/Shared/MessageTranslations/MessageTranslationHelper.cs +++ b/WheelWizard/Shared/MessageTranslations/MessageTranslationHelper.cs @@ -43,6 +43,7 @@ public enum MessageTranslation Warning_ModNameInvalid = 1010, Warning_UnableToDownloadMod_Files = 1011, Warning_DolphinToolSelected = 1012, + Warning_RecompOperationCancelled = 2013, #endregion @@ -146,6 +147,10 @@ public static (string, string?) GetTranslationText(MessageTranslation msg) t("message_warning.unable_download_mod.title"), t("message_warning.unable_download_mod.extra") ), + MessageTranslation.Warning_RecompOperationCancelled => ( + t("message_warning.recomp_operation_cancelled.title"), + t("message_warning.recomp_operation_cancelled.extra") + ), #endregion diff --git a/WheelWizard/Views/Components/Button.axaml b/WheelWizard/Views/Components/Button.axaml index 56f5222ed..9e911d22d 100644 --- a/WheelWizard/Views/Components/Button.axaml +++ b/WheelWizard/Views/Components/Button.axaml @@ -9,6 +9,7 @@ + + @@ -56,6 +58,13 @@ + + - - - - - - - - - 5x (3200x2640) - - - 2x (1280x1056) - - - 6x (3840x3168) - - - 3x (1920x1584) - - - 7x (4480x3696) - - - 4x (2560x2112) - - - 8x (5120x4224) - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/WheelWizard/Views/Pages/Settings/VideoSettings.axaml.cs b/WheelWizard/Views/Pages/Settings/VideoSettings.axaml.cs index 0a7fce39f..b7808a6c6 100644 --- a/WheelWizard/Views/Pages/Settings/VideoSettings.axaml.cs +++ b/WheelWizard/Views/Pages/Settings/VideoSettings.axaml.cs @@ -9,6 +9,18 @@ namespace WheelWizard.Views.Pages.Settings; public partial class VideoSettings : UserControlBase { + private static readonly string[] ResolutionOptions = + [ + "1x (640x528)", + "2x (1280x1056)", + "3x (1920x1584)", + "4x (2560x2112)", + "5x (3200x2640)", + "6x (3840x3168)", + "7x (4480x3696)", + "8x (5120x4224)", + ]; + private readonly bool _settingsAreDisabled; [Inject] @@ -21,20 +33,18 @@ public VideoSettings() DisabledWarningText.IsVisible = _settingsAreDisabled; VideoBorder.IsEnabled = !_settingsAreDisabled; + foreach (var resolution in ResolutionOptions) + ResolutionDropdown.Items.Add(resolution); + if (!_settingsAreDisabled) LoadSettings(); ForceLoadSettings(); // Attach event handlers after loading settings to avoid unwanted triggers - foreach (RadioButton rb in ResolutionStackPanel.Children) - { - rb.Checked += UpdateResolution; - } - + ResolutionDropdown.SelectionChanged += ResolutionDropdown_OnSelectionChanged; VSyncButton.IsCheckedChanged += VSync_OnClick; RecommendedButton.IsCheckedChanged += Recommended_OnClick; ShowFPSButton.IsCheckedChanged += ShowFPS_OnClick; - RemoveBlurButton.IsCheckedChanged += RemoveBlur_OnClick; RendererDropdown.SelectionChanged += RendererDropdown_OnSelectionChanged; DisableForce.IsCheckedChanged += ClickForceWiimote; LaunchWithDolphin.IsCheckedChanged += ClickLaunchWithDolphinWindow; @@ -56,16 +66,11 @@ private void LoadSettings() VSyncButton.IsChecked = SettingsService.Get(SettingsService.VSYNC); RecommendedButton.IsChecked = SettingsService.Get(SettingsService.RECOMMENDED_SETTINGS); ShowFPSButton.IsChecked = SettingsService.Get(SettingsService.SHOW_FPS); - RemoveBlurButton.IsChecked = SettingsService.Get(SettingsService.REMOVE_BLUR); DisableForce.IsChecked = SettingsService.Get(SettingsService.FORCE_WIIMOTE); LaunchWithDolphin.IsChecked = SettingsService.Get(SettingsService.LAUNCH_WITH_DOLPHIN); - var finalResolution = SettingsService.Get(SettingsService.INTERNAL_RESOLUTION); - foreach (RadioButton radioButton in ResolutionStackPanel.Children) - { - var tag = radioButton.Tag?.ToString(); - radioButton.IsChecked = string.Equals(tag, finalResolution.ToString(), StringComparison.Ordinal); - } + var resolution = SettingsService.Get(SettingsService.INTERNAL_RESOLUTION); + ResolutionDropdown.SelectedIndex = resolution is >= 1 and <= 8 ? resolution - 1 : -1; } private void ForceLoadSettings() @@ -84,13 +89,10 @@ private void ForceLoadSettings() } } - private void UpdateResolution(object? sender, RoutedEventArgs e) + private void ResolutionDropdown_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) { - if (sender is RadioButton radioButton && radioButton.IsChecked == true) - { - if (int.TryParse(radioButton.Tag?.ToString(), out var resolution)) - SettingsService.Set(SettingsService.INTERNAL_RESOLUTION, resolution); - } + if (ResolutionDropdown.SelectedIndex >= 0) + SettingsService.Set(SettingsService.INTERNAL_RESOLUTION, ResolutionDropdown.SelectedIndex + 1); } private void VSync_OnClick(object? sender, RoutedEventArgs e) @@ -108,11 +110,6 @@ private void ShowFPS_OnClick(object? sender, RoutedEventArgs e) SettingsService.Set(SettingsService.SHOW_FPS, ShowFPSButton.IsChecked == true); } - private void RemoveBlur_OnClick(object? sender, RoutedEventArgs e) - { - SettingsService.Set(SettingsService.REMOVE_BLUR, RemoveBlurButton.IsChecked == true); - } - private void RendererDropdown_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) { var selectedDisplayName = RendererDropdown.SelectedItem?.ToString(); diff --git a/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml b/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml index 7002753dd..618f13c08 100644 --- a/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml +++ b/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml @@ -8,210 +8,287 @@ x:Class="WheelWizard.Views.Pages.Settings.WhWzSettings"> - - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + - + - - - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml.cs b/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml.cs index 1712481d9..ad19baa3e 100644 --- a/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml.cs +++ b/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml.cs @@ -1,11 +1,9 @@ using System.Runtime.InteropServices; using Avalonia.Controls; using Avalonia.Interactivity; -using Avalonia.Media; using Avalonia.Platform.Storage; using Avalonia.Threading; using Serilog; -using WheelWizard.DolphinInstaller; using WheelWizard.Helpers; using WheelWizard.Services; using WheelWizard.Settings; @@ -13,7 +11,7 @@ using WheelWizard.Shared.DependencyInjection; using WheelWizard.Shared.MessageTranslations; using WheelWizard.Views.Popups.Generic; -using Button = WheelWizard.Views.Components.Button; +using SettingsButton = WheelWizard.Views.Components.Button; namespace WheelWizard.Views.Pages.Settings; @@ -38,15 +36,11 @@ private sealed record LanguageDropdownItem(string Key, string DisplayName) [Inject] private IDolphinSettingManager DolphinSettingsService { get; set; } = null!; - [Inject] - private ILinuxDolphinInstaller LinuxDolphinInstallerService { get; set; } = null!; - public WhWzSettings() { InitializeComponent(); ConfigureLocationFieldsForActiveFrontend(); - AutoFillPaths(); - TogglePathSettings(false); + UpdateLocationRows(); LoadSettings(); UpdateAppDataLocationUi(); _pageLoaded = true; @@ -58,6 +52,7 @@ private void ConfigureLocationFieldsForActiveFrontend() { var recompEnabled = SettingsService.IsRecompModeActive(); DolphinExecutableField.IsVisible = !recompEnabled; + GameLocationBorder.CornerRadius = recompEnabled ? new Avalonia.CornerRadius(12, 12, 5, 5) : new Avalonia.CornerRadius(5); DolphinUserFolderLabel.Text = recompEnabled ? $"{t("option.dolphin_user_path")} ({t("helper_text.optional")})" : t("option.dolphin_user_path"); @@ -115,7 +110,7 @@ private void RefreshLanguageDropdown() private void RefreshLocalizedCodeText() { - MKGameFieldLabel.TipText = t("helper_text.end_with_x") + " .iso/.gcm/.gcz/.ciso/.wbfs/.wia/.rvz"; + MarioKartHelperText.Text = t("helper_text.end_with_x") + " .iso/.gcm/.gcz/.ciso/.wbfs/.wia/.rvz"; TranslationsPercentageText.Text = t("text.language_translated_by", t("value.language.z_translators")); TranslationsPercentageText.IsVisible = t("value.language.z_translators") != "-"; } @@ -129,26 +124,6 @@ private static string ScaleToString(double scale) return t("state.custom") + ": " + percentageString; } - private void AutoFillPaths() - { - // Recomp first-install owns Dolphin-data discovery so it can ask before sharing saves. - // Dolphin mode keeps the existing convenience auto-fill behavior. - if (SettingsService.IsRecompModeActive()) - return; - - if (DolphinExeInput.Text != "") - return; - - var folderPath = PathManager.TryFindUserFolderPath(); - if (!string.IsNullOrEmpty(folderPath)) - DolphinUserPathInput.Text = folderPath; - } - - private void AssignWrappedDolphinExeInput(string inputText) - { - DolphinExeInput.Text = EnvHelper.SingleQuotePath(inputText); - } - private async void DolphinExeBrowse_OnClick(object sender, RoutedEventArgs e) { var executableFileType = new FilePickerFileType("Executable files") @@ -164,61 +139,22 @@ private async void DolphinExeBrowse_OnClick(object sender, RoutedEventArgs e) if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - // Made this more extensible for future support for e.g. snap - var dolphinPaths = new (Func IsInstalled, string Path)[] - { - (IsFlatpakDolphinInstalled, "flatpak run org.DolphinEmu.dolphin-emu"), - (IsNativeDolphinInstalled, "dolphin-emu"), - }; + const string selectFile = "Select executable file"; + const string enterCommand = "Enter launch command"; + var choice = await new OptionsWindow() + .SetWindowTitle("Change Dolphin executable") + .AddOption("FileImport", selectFile, () => { }) + .AddOption("Code", enterCommand, () => { }) + .AwaitAnswer(); - foreach (var (isInstalled, path) in dolphinPaths) + if (choice == enterCommand) { - if (isInstalled()) - { - var result = await new YesNoWindow() - .SetMainText(t("question.dolphin_program_found.title")) - .SetExtraText($"{t("question.dolphin_program_found.extra")}\n{path}") - .AwaitAnswer(); - - if (result) - { - DolphinExeInput.Text = path; - return; - } - } + await EnterLinuxDolphinCommandAsync(); + return; } - if (!IsFlatpakDolphinInstalled()) - { - var wantsAutomaticInstall = await new YesNoWindow() - .SetMainText(t("question.dolphin_flatpak.title")) - .SetExtraText(t("question.dolphin_flatpak.extra")) - .SetButtonText(t("action.install"), t("action.do_manually")) - .AwaitAnswer(); - if (wantsAutomaticInstall) - { - var progressWindow = new ProgressWindow() - .SetGoal(t("progress.installing_dolphin")) - .SetExtraText(t("progress.this_may_take_a_while")); - TogglePathSettings(true); - progressWindow.Show(); - var progress = new Progress(progressWindow.UpdateProgress); - var installResult = await LinuxDolphinInstallerService.InstallFlatpakDolphin(progress); - progressWindow.Close(); - if (installResult.IsFailure) - { - await new MessageBoxWindow() - .SetMessageType(MessageBoxWindow.MessageType.Error) - .SetTitleText("Failed to install Dolphin") - .SetInfoText(installResult.Error.Message) - .ShowDialog(); - return; - } - - DolphinExeInput.Text = "flatpak run org.DolphinEmu.dolphin-emu"; - return; - } - } + if (choice != selectFile) + return; } if (EnvHelper.IsFlatpakSandboxed()) @@ -239,7 +175,7 @@ private async void DolphinExeBrowse_OnClick(object sender, RoutedEventArgs e) if (result) { - AssignWrappedDolphinExeInput(dolphinAppPath); + await ApplyLocationSettingAsync(SettingsService.DOLPHIN_LOCATION, EnvHelper.SingleQuotePath(dolphinAppPath)); return; } } @@ -257,7 +193,7 @@ private async void DolphinExeBrowse_OnClick(object sender, RoutedEventArgs e) return; var executablePath = Path.Combine(resolvedFolder, "Contents", "MacOS", "Dolphin"); - AssignWrappedDolphinExeInput(executablePath); + await ApplyLocationSettingAsync(SettingsService.DOLPHIN_LOCATION, EnvHelper.SingleQuotePath(executablePath)); } return; // do not do normal selection for MacOS @@ -275,23 +211,29 @@ private async void DolphinExeBrowse_OnClick(object sender, RoutedEventArgs e) } // On Windows, the file path is directly used as the executable, not in some command - DolphinExeInput.Text = filePath; + await ApplyLocationSettingAsync(SettingsService.DOLPHIN_LOCATION, filePath); } else { - AssignWrappedDolphinExeInput(filePath); + await ApplyLocationSettingAsync(SettingsService.DOLPHIN_LOCATION, EnvHelper.SingleQuotePath(filePath)); } } } - private bool IsFlatpakDolphinInstalled() + private async Task EnterLinuxDolphinCommandAsync() { - return LinuxDolphinInstallerService.IsDolphinInstalledInFlatpak(); - } + var currentValue = PathManager.DolphinFilePath; + var command = await new TextInputWindow() + .SetMainText("Enter Dolphin launch command") + .SetExtraText("Enter the terminal command Wheel Wizard should use to launch Dolphin.") + .SetPlaceholderText("Example: flatpak run org.DolphinEmu.dolphin-emu") + .SetInitialText(IsConfiguredExecutableFile(currentValue) ? string.Empty : currentValue) + .SetButtonText(t("action.cancel"), t("action.save")) + .SetValidation((_, value) => string.IsNullOrWhiteSpace(value) ? Fail("Enter a launch command.") : Ok()) + .ShowDialog(); - private bool IsNativeDolphinInstalled() - { - return LinuxDolphinInstallerService.IsDolphinInstalledNative(); + if (command != null) + await ApplyLocationSettingAsync(SettingsService.DOLPHIN_LOCATION, command.Trim()); } private async void GameLocationBrowse_OnClick(object sender, RoutedEventArgs e) @@ -304,15 +246,13 @@ private async void GameLocationBrowse_OnClick(object sender, RoutedEventArgs e) var filePath = await FilePickerHelper.OpenSingleFileAsync("Select Mario Kart Wii Game File", [fileType]); if (!string.IsNullOrEmpty(filePath)) { - MarioKartInput.Text = filePath; + await ApplyLocationSettingAsync(SettingsService.GAME_LOCATION, filePath); } } private async void DolphinUserPathBrowse_OnClick(object sender, RoutedEventArgs e) { - // Detect Data folder based on the Dolphin path currently in the input field - // (which may have been edited but not yet saved) - var currentDolphinPath = DolphinExeInput.Text; + var currentDolphinPath = PathManager.DolphinFilePath; var folderPath = string.IsNullOrWhiteSpace(currentDolphinPath) ? PathManager.TryFindUserFolderPath() : PathManager.TryFindUserFolderPath(currentDolphinPath); @@ -327,7 +267,7 @@ private async void DolphinUserPathBrowse_OnClick(object sender, RoutedEventArgs if (result) { - DolphinUserPathInput.Text = folderPath; + await ApplyLocationSettingAsync(SettingsService.USER_FOLDER_PATH, folderPath); return; } } @@ -348,7 +288,7 @@ private async void DolphinUserPathBrowse_OnClick(object sender, RoutedEventArgs { var resolvedFolder = await ResolveSelectedFolderPathAsync(folders[0]); if (!string.IsNullOrWhiteSpace(resolvedFolder)) - DolphinUserPathInput.Text = resolvedFolder; + await ApplyLocationSettingAsync(SettingsService.USER_FOLDER_PATH, resolvedFolder); } return; } @@ -361,44 +301,58 @@ private async void DolphinUserPathBrowse_OnClick(object sender, RoutedEventArgs { var resolvedFolder = await ResolveSelectedFolderPathAsync(manualFolders[0]); if (!string.IsNullOrWhiteSpace(resolvedFolder)) - DolphinUserPathInput.Text = resolvedFolder; + await ApplyLocationSettingAsync(SettingsService.USER_FOLDER_PATH, resolvedFolder); } } } - private async void SaveButton_OnClick(object sender, RoutedEventArgs e) + private async Task ApplyLocationSettingAsync(Setting setting, string path) { - var oldPath1 = (string)SettingsService.DOLPHIN_LOCATION.Get(); - var oldPath2 = (string)SettingsService.GAME_LOCATION.Get(); - var oldPath3 = (string)SettingsService.USER_FOLDER_PATH.Get(); - - var dolphinPath = DolphinExeInput.Text ?? string.Empty; - var gamePath = MarioKartInput.Text ?? string.Empty; - var userPathInput = DolphinUserPathInput.Text; - var userPath = string.IsNullOrWhiteSpace(userPathInput) ? string.Empty : userPathInput.TrimEnd(Path.DirectorySeparatorChar); - - var path1 = SettingsService.DOLPHIN_LOCATION.Set(dolphinPath); - var path2 = SettingsService.GAME_LOCATION.Set(gamePath); - var path3 = SettingsService.USER_FOLDER_PATH.Set(userPath); - // These 3 lines is only saving the settings - TogglePathSettings(false); - if (!(SettingsService.PathsSetupCorrectly() && path1 && path2 && path3)) - await MessageTranslationHelper.AwaitMessageAsync(MessageTranslation.Warning_InvalidPathSettings); - else - { - await MessageTranslationHelper.AwaitMessageAsync(MessageTranslation.Success_PathSettingsSaved); + var normalizedPath = + setting == SettingsService.USER_FOLDER_PATH ? path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) : path; + var previousPath = (string)setting.Get(); - // This is not really the best approach, but it works for now - if (oldPath1 + oldPath2 + oldPath3 != DolphinExeInput.Text + MarioKartInput.Text + DolphinUserPathInput.Text) - DolphinSettingsService.ReloadSettings(); + if (!setting.Set(normalizedPath)) + { + await MessageTranslationHelper.AwaitMessageAsync(MessageTranslation.Warning_InvalidPathSettings); + UpdateLocationRows(); + return false; } + + UpdateLocationRows(); + if (!string.Equals(previousPath, normalizedPath, StringComparison.Ordinal) && SettingsService.PathsSetupCorrectly()) + DolphinSettingsService.ReloadSettings(); + + await MessageTranslationHelper.AwaitMessageAsync(MessageTranslation.Success_PathSettingsSaved); + return true; } - private void CancelButton_OnClick(object sender, RoutedEventArgs e) => TogglePathSettings(false); + private void DolphinExecutableOpen_OnClick(object sender, RoutedEventArgs e) => OpenContainingFolder(PathManager.DolphinFilePath); + + private void GameLocationOpen_OnClick(object sender, RoutedEventArgs e) => OpenContainingFolder(PathManager.GameFilePath); - private void EditButton_OnClick(object sender, RoutedEventArgs e) => TogglePathSettings(true); + private void DolphinUserFolderOpen_OnClick(object sender, RoutedEventArgs e) + { + if (Directory.Exists(PathManager.UserFolderPath)) + FilePickerHelper.OpenFolderInFileManager(PathManager.UserFolderPath); + } + + private static void OpenContainingFolder(string filePath) + { + try + { + var unquotedPath = filePath.Trim().Trim('\'', '"'); + var folderPath = Path.GetDirectoryName(Path.GetFullPath(unquotedPath)); + if (!string.IsNullOrWhiteSpace(folderPath) && Directory.Exists(folderPath)) + FilePickerHelper.OpenFolderInFileManager(folderPath); + } + catch + { + // Commands such as Flatpak launch strings do not have a folder to open. + } + } - private void WhWzFolder_Click(object sender, RoutedEventArgs e) + private void AppDataLocationOpen_OnClick(object sender, RoutedEventArgs e) { if (!Directory.Exists(PathManager.WheelWizardAppdataPath)) Directory.CreateDirectory(PathManager.WheelWizardAppdataPath); @@ -406,72 +360,87 @@ private void WhWzFolder_Click(object sender, RoutedEventArgs e) FilePickerHelper.OpenFolderInFileManager(PathManager.WheelWizardAppdataPath); } - private void GameFileFolder_Click(object? sender, RoutedEventArgs e) + private void UpdateLocationRows() { - if (!Directory.Exists(PathManager.RiivolutionWhWzFolderPath)) - return; //Button should be disabled - FilePickerHelper.OpenFolderInFileManager(PathManager.RiivolutionWhWzFolderPath); + SetLocationRowState( + DolphinExecutableCompleteIcon, + DolphinExecutableWarningIcon, + DolphinExecutableChangeButton, + SettingsService.DOLPHIN_LOCATION.IsValid() && !string.IsNullOrWhiteSpace(PathManager.DolphinFilePath) + ); + SetLocationRowState( + GameLocationCompleteIcon, + GameLocationWarningIcon, + GameLocationChangeButton, + SettingsService.GAME_LOCATION.IsValid() && !string.IsNullOrWhiteSpace(PathManager.GameFilePath) + ); + SetLocationRowState( + DolphinUserFolderCompleteIcon, + DolphinUserFolderWarningIcon, + DolphinUserFolderChangeButton, + SettingsService.USER_FOLDER_PATH.IsValid() + ); + + LocationWarningIcon.IsVisible = !SettingsService.PathsSetupCorrectly(); + DolphinExecutableValueText.Text = string.IsNullOrWhiteSpace(PathManager.DolphinFilePath) + ? t("helper_text.end_with_exe") + : PathManager.DolphinFilePath; + DolphinExecutableOpenButton.IsVisible = !OperatingSystem.IsLinux() || IsConfiguredExecutableFile(PathManager.DolphinFilePath); + DolphinExecutableOpenButton.IsEnabled = + DolphinExecutableOpenButton.IsVisible && CanOpenContainingFolder(PathManager.DolphinFilePath); + GameLocationOpenButton.IsEnabled = CanOpenContainingFolder(PathManager.GameFilePath); + DolphinUserFolderOpenButton.IsEnabled = Directory.Exists(PathManager.UserFolderPath); } - private void TogglePathSettings(bool enable) + private static void SetLocationRowState(PathIcon completeIcon, PathIcon warningIcon, SettingsButton changeButton, bool isValid) { - if (enable) - { - LocationBorder.BorderBrush = new SolidColorBrush(ViewUtils.Colors.Neutral900); - } - else if (!SettingsService.PathsSetupCorrectly()) + completeIcon.IsVisible = isValid; + warningIcon.IsVisible = !isValid; + changeButton.Variant = isValid ? SettingsButton.ButtonsVariantType.Default : SettingsButton.ButtonsVariantType.Warning; + } + + private static bool CanOpenContainingFolder(string filePath) + { + try { - LocationBorder.BorderBrush = new SolidColorBrush(ViewUtils.Colors.Warning400); - LocationEditButton.Variant = Button.ButtonsVariantType.Warning; - LocationWarningIcon.IsVisible = true; - LocationBorderBlur.Background = new SolidColorBrush(ViewUtils.Colors.Warning600); + var unquotedPath = filePath.Trim().Trim('\'', '"'); + var folderPath = Path.GetDirectoryName(Path.GetFullPath(unquotedPath)); + return !string.IsNullOrWhiteSpace(folderPath) && Directory.Exists(folderPath); } - else + catch { - LocationBorder.BorderBrush = new SolidColorBrush(ViewUtils.Colors.Primary400); - LocationEditButton.Variant = Button.ButtonsVariantType.Primary; - LocationWarningIcon.IsVisible = false; - LocationBorderBlur.Background = new SolidColorBrush(ViewUtils.Colors.Primary600); + return false; } - - LocationBorderBlur.IsVisible = !enable; - LocationInputFields.IsEnabled = enable; - LocationEditButton.IsVisible = !enable; - LocationSaveButton.IsVisible = enable; - LocationCancelButton.IsVisible = enable; - - DolphinExeInput.Text = PathManager.DolphinFilePath; - MarioKartInput.Text = PathManager.GameFilePath; - DolphinUserPathInput.Text = PathManager.UserFolderPath; - OpenGameFolderButton.IsEnabled = Directory.Exists(PathManager.RiivolutionWhWzFolderPath); - UpdateAppDataLocationUi(); } + private static bool IsConfiguredExecutableFile(string value) => File.Exists(value.Trim().Trim('\'', '"')); + private void UpdateAppDataLocationUi() { - var currentPath = PathManager.WheelWizardAppdataPath; - AppDataLocationInput.Text = currentPath; - AppDataLocationInput.CaretIndex = currentPath.Length; - ToolTip.SetTip(AppDataLocationInput, currentPath); - var statusText = _isMovingAppData ? t("status.data_folder.moving") : PathManager.IsUsingCustomWheelWizardAppdataPath ? t("status.data_folder.custom") - : t("status.data_folder.default"); + : string.Empty; AppDataLocationStatus.Text = statusText; - AppDataLocationBrowseButton.IsEnabled = !_isMovingAppData; + AppDataLocationStatus.IsVisible = !string.IsNullOrEmpty(statusText); + AppDataLocationChangeButton.IsEnabled = !_isMovingAppData; AppDataLocationResetButton.IsEnabled = !_isMovingAppData && PathManager.IsUsingCustomWheelWizardAppdataPath; + AppDataLocationOpenButton.IsEnabled = !_isMovingAppData; ToolTip.SetTip(AppDataLocationResetButton, PathManager.DefaultWheelWizardAppdataFolderPath); } private void SetAppDataLocationBusyState(bool isBusy) { _isMovingAppData = isBusy; - AppDataLocationBrowseButton.IsEnabled = !isBusy; + AppDataLocationChangeButton.IsEnabled = !isBusy; AppDataLocationResetButton.IsEnabled = !isBusy && PathManager.IsUsingCustomWheelWizardAppdataPath; + AppDataLocationOpenButton.IsEnabled = !isBusy; if (isBusy) + { AppDataLocationStatus.Text = t("status.data_folder.moving"); + AppDataLocationStatus.IsVisible = true; + } } private async Task ConfirmAndMoveAppDataAsync(string targetPath) @@ -682,7 +651,7 @@ private async Task HandleFailedAppdataMoveAsync(DirectoryMoveContentsResult move } } - private async void AppDataLocationBrowse_OnClick(object sender, RoutedEventArgs e) + private async void AppDataLocationChange_OnClick(object sender, RoutedEventArgs e) { if (_isMovingAppData) return; @@ -698,12 +667,9 @@ private async void AppDataLocationBrowse_OnClick(object sender, RoutedEventArgs if (folders == null || folders.Count == 0) return; - var selected = folders[0]; - var resolvedPath = await ResolveSelectedFolderPathAsync(selected); - if (string.IsNullOrWhiteSpace(resolvedPath)) - return; - - await ConfirmAndMoveAppDataAsync(resolvedPath); + var resolvedPath = await ResolveSelectedFolderPathAsync(folders[0]); + if (!string.IsNullOrWhiteSpace(resolvedPath)) + await ConfirmAndMoveAppDataAsync(resolvedPath); } private async void AppDataLocationReset_OnClick(object sender, RoutedEventArgs e) diff --git a/WheelWizard/Views/Pages/SettingsPage.axaml.cs b/WheelWizard/Views/Pages/SettingsPage.axaml.cs index bf52dd0df..36ee8f0dd 100644 --- a/WheelWizard/Views/Pages/SettingsPage.axaml.cs +++ b/WheelWizard/Views/Pages/SettingsPage.axaml.cs @@ -1,6 +1,7 @@ using Avalonia.Controls; using Avalonia.Interactivity; using WheelWizard.Settings; +using WheelWizard.Shared; using WheelWizard.Shared.DependencyInjection; using WheelWizard.Views.Pages.Settings; using WheelWizard.Views.Popups; @@ -26,9 +27,7 @@ public SettingsPage(UserControl initialSettingsPage) UpdateTabVisibility(); _settingsSignalSubscription = SettingsSignalBus.Subscribe(OnSettingChanged); -#if DEBUG - DevButton.IsVisible = true; -#endif + DevButton.IsVisible = DevelopmentMode.IsEnabled; SettingsContent.Content = initialSettingsPage; SetCheckedTopBarButton(initialSettingsPage); @@ -98,4 +97,11 @@ private void SetCheckedTopBarButton(UserControl settingsPage) } private void DevButton_OnClick(object? sender, RoutedEventArgs e) => new DevToolWindow().Show(); + + public void HideDevelopmentFeatures() + { + DevButton.IsVisible = false; + if (SettingsContent.Content is AppInfo) + SettingsContent.Content = new AppInfo(); + } } diff --git a/WheelWizard/Views/Popups/DevToolWindow.axaml b/WheelWizard/Views/Popups/DevToolWindow.axaml index 3e8367215..ebd155a8a 100644 --- a/WheelWizard/Views/Popups/DevToolWindow.axaml +++ b/WheelWizard/Views/Popups/DevToolWindow.axaml @@ -47,6 +47,9 @@ + @@ -72,4 +75,4 @@ - \ No newline at end of file + diff --git a/WheelWizard/Views/Popups/DevToolWindow.axaml.cs b/WheelWizard/Views/Popups/DevToolWindow.axaml.cs index 26e644ac1..0a3722d0d 100644 --- a/WheelWizard/Views/Popups/DevToolWindow.axaml.cs +++ b/WheelWizard/Views/Popups/DevToolWindow.axaml.cs @@ -4,6 +4,7 @@ using WheelWizard.Helpers; using WheelWizard.Services.Launcher.Helpers; using WheelWizard.Services.LiveData; +using WheelWizard.Shared; using WheelWizard.Shared.DependencyInjection; using WheelWizard.Shared.MessageTranslations; using WheelWizard.Utilities; @@ -59,6 +60,14 @@ private void HttpHelperOff_OnClick(object sender, RoutedEventArgs e) => private void ClearCache_OnClick(object sender, RoutedEventArgs e) => ((MemoryCache)Cache).Clear(); + private void HideDevelopmentFeatures_OnClick(object sender, RoutedEventArgs e) + { + DevelopmentMode.Hide(); + HttpClientHelper.FakeConnectionToInternet = true; + ViewUtils.GetLayout().HideDevelopmentFeatures(); + Close(); + } + private async void MiiChannel_OnClick(object? sender, RoutedEventArgs e) => await DolphinLaunchHelper.LaunchDolphin(" -b -n 0001000248414341"); diff --git a/WheelWizard/Views/Popups/Generic/MiiRenderingSetupPopup.axaml b/WheelWizard/Views/Popups/Generic/MiiRenderingSetupPopup.axaml index 3e6382d01..ce39fe888 100644 --- a/WheelWizard/Views/Popups/Generic/MiiRenderingSetupPopup.axaml +++ b/WheelWizard/Views/Popups/Generic/MiiRenderingSetupPopup.axaml @@ -49,7 +49,7 @@ + Text="Skip opens Wheel Wizard without offline 3D Mii rendering." /> - 0 ? $"{FormatMegabytes(progress.BytesDownloaded)} downloaded" : "Working..."; } - private void CloseButton_OnClick(object? sender, RoutedEventArgs e) - { - _downloadCancellationTokenSource?.Cancel(); - Close(); - } - private void SkipButton_OnClick(object? sender, RoutedEventArgs e) { if (_downloadCancellationTokenSource != null) @@ -115,8 +109,6 @@ private void SetBusyState(bool busy, string statusText) StatusTextBlock.Text = statusText; DownloadButton.IsEnabled = !busy; SkipButton.IsEnabled = !busy; - CloseButton.IsEnabled = true; - CloseButton.Text = busy ? "Close and Exit" : "Close"; } private void ShowError(string message) diff --git a/WheelWizard/Views/Popups/Generic/ProgressWindow.axaml b/WheelWizard/Views/Popups/Generic/ProgressWindow.axaml index c2813749f..4fc553d50 100644 --- a/WheelWizard/Views/Popups/Generic/ProgressWindow.axaml +++ b/WheelWizard/Views/Popups/Generic/ProgressWindow.axaml @@ -17,19 +17,19 @@ - + - - + + diff --git a/WheelWizard/Views/Popups/Generic/ProgressWindow.axaml.cs b/WheelWizard/Views/Popups/Generic/ProgressWindow.axaml.cs index b501a3a63..bf4349255 100644 --- a/WheelWizard/Views/Popups/Generic/ProgressWindow.axaml.cs +++ b/WheelWizard/Views/Popups/Generic/ProgressWindow.axaml.cs @@ -7,11 +7,19 @@ namespace WheelWizard.Views.Popups.Generic; public partial class ProgressWindow : PopupContent { - private Stopwatch _stopwatch = new(); + private const double EtaWarmupSeconds = 5; + private const double EtaIncreaseSmoothingSeconds = 5; + private const double EtaDecreaseSmoothingSeconds = 30; + private const double InitialSecondsPerRemainingPercent = 2; + + private readonly Stopwatch _stopwatch = new(); + private readonly object _progressLock = new(); private int _progress = 0; private double? _totalMb = null; - private DispatcherTimer _updateTimer; + private readonly DispatcherTimer _updateTimer; private CancellationTokenSource? _downloadCancellationTokenSource; + private double? _smoothedRemainingSeconds; + private double _lastEstimateUpdateSeconds; public bool WasCancellationRequested { get; private set; } @@ -29,7 +37,13 @@ public ProgressWindow(string windowTitle) protected override void BeforeOpen() { - _stopwatch.Start(); + lock (_progressLock) + { + _smoothedRemainingSeconds = null; + _lastEstimateUpdateSeconds = 0; + } + + _stopwatch.Restart(); _updateTimer.Start(); } @@ -47,20 +61,26 @@ private void UpdateTimer_Tick(object? sender, EventArgs e) private void InternalUpdate() { var elapsedSeconds = _stopwatch.Elapsed.TotalSeconds; - var remainingSeconds = (100 - _progress) / (_progress / elapsedSeconds); + int progress; + double? remainingSeconds; + lock (_progressLock) + { + remainingSeconds = EstimateRemainingSeconds(elapsedSeconds); + progress = _progress; + } - var remainingText = _progress <= 0 ? t("state.unknown") : tTime((int)remainingSeconds); + var remainingText = remainingSeconds is null ? t("state.unknown") : tTime((int)Math.Ceiling(remainingSeconds.Value)); var bottomText = $"{t("progress.estimated_time_remaining")} {remainingText}"; - if (_totalMb != null) + if (_totalMb != null && elapsedSeconds > 0) { - var downloadedMb = (_progress / 100.0) * (double)_totalMb; + var downloadedMb = (progress / 100.0) * (double)_totalMb; bottomText = $"{t("attribute.speed")}: {downloadedMb / elapsedSeconds:F2} MB/s | {bottomText}"; } LiveTextBlock.Text = bottomText; - ProgressBar.Value = _progress; + ProgressBar.Value = progress; } public ProgressWindow SetExtraText(string mainText) @@ -83,12 +103,54 @@ public ProgressWindow SetGoal(double megaBytes) return this; } + public ProgressWindow SetIndeterminate(bool isIndeterminate = true) + { + ProgressBar.IsIndeterminate = isIndeterminate; + LiveTextBlock.IsVisible = !isIndeterminate; + return this; + } + public void UpdateProgress(int progress) { - _progress = progress; + var clampedProgress = Math.Clamp(progress, 0, 100); + lock (_progressLock) + _progress = clampedProgress; // No need to call InternalUpdate directly, it's handled by the timer } + private double? EstimateRemainingSeconds(double elapsedSeconds) + { + if (_progress >= 100) + return 0; + if (elapsedSeconds < EtaWarmupSeconds || _progress <= 0) + return null; + + // Whole-operation progress remains useful when a backend reports only a few, unevenly + // spaced updates. The small uncertainty floor prevents an early jump to a few seconds when + // a setup phase begins at an already-weighted percentage. + var remainingProgress = 100 - _progress; + var averageRemainingSeconds = elapsedSeconds * remainingProgress / _progress; + var rawRemainingSeconds = Math.Max(averageRemainingSeconds, remainingProgress * InitialSecondsPerRemainingPercent); + if (!double.IsFinite(rawRemainingSeconds) || rawRemainingSeconds < 0) + return null; + + if (_smoothedRemainingSeconds is null) + { + _smoothedRemainingSeconds = rawRemainingSeconds; + } + else + { + var updateSeconds = Math.Max(0, elapsedSeconds - _lastEstimateUpdateSeconds); + var smoothingSeconds = + rawRemainingSeconds > _smoothedRemainingSeconds ? EtaIncreaseSmoothingSeconds : EtaDecreaseSmoothingSeconds; + var weight = 1 - Math.Exp(-updateSeconds / smoothingSeconds); + _smoothedRemainingSeconds += weight * (rawRemainingSeconds - _smoothedRemainingSeconds.Value); + } + + _lastEstimateUpdateSeconds = elapsedSeconds; + return _smoothedRemainingSeconds; + } + public ProgressWindow SetCancellationTokenSource(CancellationTokenSource? cancellationTokenSource) { _downloadCancellationTokenSource = cancellationTokenSource; diff --git a/WheelWizard/Views/Popups/Generic/TextInputWindow.axaml b/WheelWizard/Views/Popups/Generic/TextInputWindow.axaml index aec0743c4..32f8560d4 100644 --- a/WheelWizard/Views/Popups/Generic/TextInputWindow.axaml +++ b/WheelWizard/Views/Popups/Generic/TextInputWindow.axaml @@ -12,12 +12,13 @@ - - + + - \ No newline at end of file + diff --git a/WheelWizard/Views/Popups/Generic/YesNoWindow.axaml b/WheelWizard/Views/Popups/Generic/YesNoWindow.axaml index 77bee9e47..ba5af6b58 100644 --- a/WheelWizard/Views/Popups/Generic/YesNoWindow.axaml +++ b/WheelWizard/Views/Popups/Generic/YesNoWindow.axaml @@ -10,13 +10,13 @@ - - + + - \ No newline at end of file + diff --git a/WheelWizard/Views/Styles/Styles/DropdownStyles.axaml b/WheelWizard/Views/Styles/Styles/DropdownStyles.axaml index 4fd15f541..bffc404d6 100644 --- a/WheelWizard/Views/Styles/Styles/DropdownStyles.axaml +++ b/WheelWizard/Views/Styles/Styles/DropdownStyles.axaml @@ -16,9 +16,9 @@ - - - + + + + + + + + + + +