From bc154a0f4091ba99915e56dddf3d9e739d592a67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 2 May 2026 14:30:07 +0000 Subject: [PATCH 1/4] Initial plan From 8c7f1e2b8fd757cf53e5177ca0a57f378617a111 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 2 May 2026 14:41:29 +0000 Subject: [PATCH 2/4] =?UTF-8?q?NuGet=E3=83=97=E3=83=A9=E3=82=B0=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E3=82=B9=E3=83=88=E3=82=A2=E3=81=AE=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=AE=9F=E8=A3=85=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/Freeesia/WindowTranslator/sessions/5fefad82-801f-4158-ad6f-9d7500bf50ed Co-authored-by: Freeesia <9002657+Freeesia@users.noreply.github.com> --- .../Modules/PluginStore/NuGetPluginService.cs | 393 ++++++++++++++++++ .../Modules/PluginStore/PluginStoreView.xaml | 252 +++++++++++ .../PluginStore/PluginStoreView.xaml.cs | 80 ++++ .../PluginStore/PluginStoreViewModel.cs | 264 ++++++++++++ .../Modules/Settings/AllSettingsDialog.xaml | 4 + .../Modules/Settings/AllSettingsViewModel.cs | 5 + WindowTranslator/Program.cs | 6 + .../Properties/Resources.Designer.cs | 311 +++++++++----- WindowTranslator/Properties/Resources.ar.resx | 51 +++ WindowTranslator/Properties/Resources.cs.resx | 51 +++ WindowTranslator/Properties/Resources.de.resx | 51 +++ WindowTranslator/Properties/Resources.en.resx | 51 +++ WindowTranslator/Properties/Resources.es.resx | 51 +++ WindowTranslator/Properties/Resources.fa.resx | 51 +++ .../Properties/Resources.fil.resx | 51 +++ WindowTranslator/Properties/Resources.fr.resx | 51 +++ WindowTranslator/Properties/Resources.hi.resx | 51 +++ WindowTranslator/Properties/Resources.id.resx | 51 +++ WindowTranslator/Properties/Resources.ko.resx | 51 +++ WindowTranslator/Properties/Resources.ms.resx | 51 +++ WindowTranslator/Properties/Resources.pl.resx | 51 +++ .../Properties/Resources.pt-BR.resx | 51 +++ WindowTranslator/Properties/Resources.resx | 51 +++ WindowTranslator/Properties/Resources.ru.resx | 51 +++ WindowTranslator/Properties/Resources.th.resx | 51 +++ WindowTranslator/Properties/Resources.tr.resx | 51 +++ WindowTranslator/Properties/Resources.vi.resx | 51 +++ .../Properties/Resources.zh-CN.resx | 51 +++ .../Properties/Resources.zh-TW.resx | 51 +++ 29 files changed, 2273 insertions(+), 113 deletions(-) create mode 100644 WindowTranslator/Modules/PluginStore/NuGetPluginService.cs create mode 100644 WindowTranslator/Modules/PluginStore/PluginStoreView.xaml create mode 100644 WindowTranslator/Modules/PluginStore/PluginStoreView.xaml.cs create mode 100644 WindowTranslator/Modules/PluginStore/PluginStoreViewModel.cs diff --git a/WindowTranslator/Modules/PluginStore/NuGetPluginService.cs b/WindowTranslator/Modules/PluginStore/NuGetPluginService.cs new file mode 100644 index 00000000..3f0c81bb --- /dev/null +++ b/WindowTranslator/Modules/PluginStore/NuGetPluginService.cs @@ -0,0 +1,393 @@ +using System.IO; +using System.IO.Compression; +using System.Net.Http; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.Logging; + +namespace WindowTranslator.Modules.PluginStore; + +/// +/// NuGet V3 REST APIを使用してプラグインパッケージの検索・インストール・管理を行うサービスです。 +/// +public sealed class NuGetPluginService : IDisposable +{ + private const string NuGetServiceIndexUrl = "https://api.nuget.org/v3/index.json"; + private const string PluginTag = "windowtranslator-plugin"; + private const string NuGetFlatContainerBase = "https://api.nuget.org/v3-flatcontainer"; + + private static readonly string UserPluginsDir = Path.Combine(PathUtility.UserDir, "plugins"); + private static readonly string ManifestPath = Path.Combine(UserPluginsDir, "nuget-manifest.json"); + + private static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNameCaseInsensitive = true, + AllowTrailingCommas = true, + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + private readonly HttpClient httpClient; + private readonly ILogger logger; + private string? searchUrl; + + public NuGetPluginService(ILogger logger) + { + this.httpClient = new HttpClient(); + this.logger = logger; + } + + /// + /// NuGetでWindowTranslatorプラグインを検索します。 + /// + public async Task> SearchPackagesAsync(CancellationToken cancellationToken = default) + { + if (this.searchUrl is null) + { + this.searchUrl = await GetSearchUrlAsync(cancellationToken).ConfigureAwait(false); + } + + var url = $"{this.searchUrl}?q=tags:{PluginTag}&take=100&semVerLevel=2.0.0&prerelease=false"; + this.logger.LogDebug("NuGet検索URL: {Url}", url); + + var response = await this.httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + + var content = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + var result = await JsonSerializer.DeserializeAsync(content, JsonOptions, cancellationToken).ConfigureAwait(false) + ?? throw new InvalidOperationException("NuGet検索結果のデシリアライズに失敗しました。"); + + this.logger.LogInformation("NuGet検索完了: {Count}件のパッケージが見つかりました。", result.Data?.Length ?? 0); + + return result.Data?.Select(d => new NuGetPackageInfo( + Id: d.PackageId ?? string.Empty, + Version: d.Version ?? string.Empty, + Title: d.Title ?? d.PackageId ?? string.Empty, + Description: d.Description ?? string.Empty, + Authors: string.Join(", ", d.Authors ?? []), + ProjectUrl: d.ProjectUrl, + LicenseUrl: d.LicenseUrl + )).ToArray() ?? []; + } + + /// + /// 指定したNuGetパッケージをインストールします。 + /// + public async Task InstallPackageAsync(string packageId, string version, IProgress? progress = null, CancellationToken cancellationToken = default) + { + var packageIdLower = packageId.ToLowerInvariant(); + var versionLower = version.ToLowerInvariant(); + var nupkgUrl = $"{NuGetFlatContainerBase}/{packageIdLower}/{versionLower}/{packageIdLower}.{versionLower}.nupkg"; + + this.logger.LogInformation("パッケージをダウンロード中: {PackageId} {Version}", packageId, version); + + // 一時ディレクトリにダウンロード + var tempDir = Path.Combine(Path.GetTempPath(), "WindowTranslatorPlugins", packageId); + Directory.CreateDirectory(tempDir); + var tempNupkgPath = Path.Combine(tempDir, $"{packageIdLower}.{versionLower}.nupkg"); + + try + { + await DownloadFileAsync(nupkgUrl, tempNupkgPath, progress, cancellationToken).ConfigureAwait(false); + + // ターゲットディレクトリを準備 + var targetDir = Path.Combine(UserPluginsDir, packageId); + // 古いファイルをバックアップして削除する前に一時フォルダへ移動 + var backupDir = $"{targetDir}.backup"; + if (Directory.Exists(targetDir)) + { + if (Directory.Exists(backupDir)) + Directory.Delete(backupDir, recursive: true); + Directory.Move(targetDir, backupDir); + } + + Directory.CreateDirectory(targetDir); + + try + { + // nupkgを展開して必要なDLLをコピー + ExtractPluginDlls(tempNupkgPath, targetDir); + this.logger.LogInformation("パッケージの展開完了: {PackageId} -> {TargetDir}", packageId, targetDir); + } + catch + { + // 失敗したら元に戻す + Directory.Delete(targetDir, recursive: true); + if (Directory.Exists(backupDir)) + Directory.Move(backupDir, targetDir); + throw; + } + + // バックアップを削除 + if (Directory.Exists(backupDir)) + Directory.Delete(backupDir, recursive: true); + + // マニフェストを更新 + await UpdateManifestAsync(packageId, version, cancellationToken).ConfigureAwait(false); + } + finally + { + // 一時ファイルを削除 + try { File.Delete(tempNupkgPath); } catch { /* ignore */ } + } + } + + /// + /// 指定したパッケージをアンインストールします。(次回起動時に適用) + /// + public async Task UninstallPackageAsync(string packageId, CancellationToken cancellationToken = default) + { + this.logger.LogInformation("パッケージをアンインストール: {PackageId}", packageId); + + var targetDir = Path.Combine(UserPluginsDir, packageId); + // 実行中のDLLがロックされている可能性があるため、削除マーカーを置く + var pendingDeleteMarker = Path.Combine(UserPluginsDir, $"{packageId}.pending-delete"); + await File.WriteAllTextAsync(pendingDeleteMarker, packageId, cancellationToken).ConfigureAwait(false); + + // マニフェストから削除 + await RemoveFromManifestAsync(packageId, cancellationToken).ConfigureAwait(false); + + this.logger.LogInformation("パッケージ {PackageId} をアンインストールキューに追加しました。再起動後に完全に削除されます。", packageId); + } + + /// + /// アプリ起動時にペンディング削除マーカーを処理します。 + /// + public void ProcessPendingDeletions() + { + if (!Directory.Exists(UserPluginsDir)) + return; + + foreach (var markerFile in Directory.GetFiles(UserPluginsDir, "*.pending-delete")) + { + try + { + var packageId = File.ReadAllText(markerFile); + var targetDir = Path.Combine(UserPluginsDir, packageId); + if (Directory.Exists(targetDir)) + { + Directory.Delete(targetDir, recursive: true); + this.logger.LogInformation("ペンディング削除を処理: {PackageId}", packageId); + } + File.Delete(markerFile); + } + catch (Exception ex) + { + this.logger.LogWarning(ex, "ペンディング削除の処理に失敗: {MarkerFile}", markerFile); + } + } + } + + /// + /// インストール済みのパッケージ一覧を取得します。 + /// + public async Task> GetInstalledPackagesAsync(CancellationToken cancellationToken = default) + { + var manifest = await LoadManifestAsync(cancellationToken).ConfigureAwait(false); + return manifest.Packages; + } + + private async Task GetSearchUrlAsync(CancellationToken cancellationToken) + { + var response = await this.httpClient.GetAsync(NuGetServiceIndexUrl, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + var content = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + var index = await JsonSerializer.DeserializeAsync(content, JsonOptions, cancellationToken).ConfigureAwait(false) + ?? throw new InvalidOperationException("NuGetサービスインデックスのデシリアライズに失敗しました。"); + + var searchEntry = index.Resources?.FirstOrDefault(r => r.Type == "SearchQueryService/3.5.0") + ?? index.Resources?.FirstOrDefault(r => r.Type?.StartsWith("SearchQueryService", StringComparison.Ordinal) == true) + ?? throw new InvalidOperationException("NuGet検索サービスURLが見つかりませんでした。"); + + return searchEntry.Id ?? throw new InvalidOperationException("NuGet検索サービスURLが空です。"); + } + + private async Task DownloadFileAsync(string url, string destPath, IProgress? progress, CancellationToken cancellationToken) + { + using var response = await this.httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + + var totalBytes = response.Content.Headers.ContentLength ?? -1; + var downloadedBytes = 0L; + + using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + using var fileStream = File.Create(destPath); + var buffer = new byte[81920]; + int bytesRead; + while ((bytesRead = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) > 0) + { + await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false); + downloadedBytes += bytesRead; + if (totalBytes > 0) + { + progress?.Report((double)downloadedBytes / totalBytes); + } + } + } + + private static void ExtractPluginDlls(string nupkgPath, string targetDir) + { + using var archive = ZipFile.OpenRead(nupkgPath); + + // 最適なTFMのlib/エントリを探す + var libEntries = archive.Entries + .Where(e => e.FullName.StartsWith("lib/", StringComparison.OrdinalIgnoreCase) + && !string.IsNullOrEmpty(e.Name) + && e.Name != "_._") + .ToList(); + + if (!libEntries.Any()) + { + throw new InvalidOperationException("パッケージにlib/フォルダが見つかりませんでした。"); + } + + // TFMを選択(net10.0-windows > net10.0 > net9.0-windows > net9.0 > ... の優先順位) + var tfmGroups = libEntries + .GroupBy(e => e.FullName.Split('/')[1]) + .ToList(); + + var selectedTfm = SelectBestTfm([.. tfmGroups.Select(g => g.Key)]); + if (selectedTfm is null) + { + throw new InvalidOperationException("互換性のあるターゲットフレームワークが見つかりませんでした。"); + } + + var selectedEntries = tfmGroups.First(g => g.Key == selectedTfm); + + foreach (var entry in selectedEntries) + { + var destPath = Path.Combine(targetDir, entry.Name); + entry.ExtractToFile(destPath, overwrite: true); + } + } + + private static string? SelectBestTfm(string[] tfms) + { + // TFMの優先度リスト(.NET 10から降順、Windows版を優先) + var orderedPrefixes = new[] + { + "net10.0-windows", + "net10.0", + "net9.0-windows", + "net9.0", + "net8.0-windows", + "net8.0", + "net7.0-windows", + "net7.0", + "net6.0-windows", + "net6.0", + "netstandard2.1", + "netstandard2.0", + }; + + foreach (var prefix in orderedPrefixes) + { + // 完全一致または前方一致(例: net10.0-windows10.0.20348.0) + var match = tfms.OrderByDescending(t => t).FirstOrDefault(t => + t.Equals(prefix, StringComparison.OrdinalIgnoreCase) + || t.StartsWith(prefix + ".", StringComparison.OrdinalIgnoreCase) + || t.StartsWith(prefix + "_", StringComparison.OrdinalIgnoreCase)); + if (match is not null) + return match; + } + + return tfms.FirstOrDefault(); + } + + private async Task UpdateManifestAsync(string packageId, string version, CancellationToken cancellationToken) + { + var manifest = await LoadManifestAsync(cancellationToken).ConfigureAwait(false); + var packages = manifest.Packages.ToList(); + var existing = packages.FindIndex(p => p.Id.Equals(packageId, StringComparison.OrdinalIgnoreCase)); + var newEntry = new InstalledPackageInfo(packageId, version, DateTime.UtcNow); + if (existing >= 0) + packages[existing] = newEntry; + else + packages.Add(newEntry); + + await SaveManifestAsync(new InstalledManifest([.. packages]), cancellationToken).ConfigureAwait(false); + } + + private async Task RemoveFromManifestAsync(string packageId, CancellationToken cancellationToken) + { + var manifest = await LoadManifestAsync(cancellationToken).ConfigureAwait(false); + var packages = manifest.Packages.Where(p => !p.Id.Equals(packageId, StringComparison.OrdinalIgnoreCase)).ToList(); + await SaveManifestAsync(new InstalledManifest([.. packages]), cancellationToken).ConfigureAwait(false); + } + + private async Task LoadManifestAsync(CancellationToken cancellationToken) + { + try + { + if (File.Exists(ManifestPath)) + { + using var fs = File.OpenRead(ManifestPath); + var manifest = await JsonSerializer.DeserializeAsync(fs, JsonOptions, cancellationToken).ConfigureAwait(false); + return manifest ?? new InstalledManifest([]); + } + } + catch (Exception ex) + { + this.logger.LogWarning(ex, "プラグインマニフェストの読み込みに失敗しました。"); + } + return new InstalledManifest([]); + } + + private static async Task SaveManifestAsync(InstalledManifest manifest, CancellationToken cancellationToken) + { + Directory.CreateDirectory(UserPluginsDir); + using var fs = File.Create(ManifestPath); + await JsonSerializer.SerializeAsync(fs, manifest, JsonOptions, cancellationToken).ConfigureAwait(false); + } + + public void Dispose() + { + this.httpClient.Dispose(); + } +} + +/// NuGetパッケージ情報 +public record NuGetPackageInfo( + string Id, + string Version, + string Title, + string Description, + string Authors, + string? ProjectUrl, + string? LicenseUrl +); + +/// インストール済みパッケージ情報 +public record InstalledPackageInfo( + string Id, + string Version, + DateTime InstalledAt +); + +/// インストール済みパッケージのマニフェスト +public record InstalledManifest(List Packages); + +// NuGet V3 API レスポンス型 +internal record NuGetServiceIndex( + [property: JsonPropertyName("resources")] NuGetServiceResource[]? Resources +); + +internal record NuGetServiceResource( + [property: JsonPropertyName("@id")] string? Id, + [property: JsonPropertyName("@type")] string? Type +); + +internal record NuGetSearchResponse( + [property: JsonPropertyName("totalHits")] int TotalHits, + [property: JsonPropertyName("data")] NuGetSearchData[]? Data +); + +internal record NuGetSearchData( + [property: JsonPropertyName("id")] string? PackageId, + [property: JsonPropertyName("version")] string? Version, + [property: JsonPropertyName("title")] string? Title, + [property: JsonPropertyName("description")] string? Description, + [property: JsonPropertyName("authors")] string[]? Authors, + [property: JsonPropertyName("projectUrl")] string? ProjectUrl, + [property: JsonPropertyName("licenseUrl")] string? LicenseUrl +); diff --git a/WindowTranslator/Modules/PluginStore/PluginStoreView.xaml b/WindowTranslator/Modules/PluginStore/PluginStoreView.xaml new file mode 100644 index 00000000..4b26ed4b --- /dev/null +++ b/WindowTranslator/Modules/PluginStore/PluginStoreView.xaml @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WindowTranslator/Modules/PluginStore/PluginStoreView.xaml.cs b/WindowTranslator/Modules/PluginStore/PluginStoreView.xaml.cs new file mode 100644 index 00000000..956ef27b --- /dev/null +++ b/WindowTranslator/Modules/PluginStore/PluginStoreView.xaml.cs @@ -0,0 +1,80 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace WindowTranslator.Modules.PluginStore; + +/// +/// PluginStoreView.xaml の相互作用ロジック +/// +public partial class PluginStoreView +{ + private bool loaded; + + public PluginStoreView() + { + InitializeComponent(); + this.IsVisibleChanged += OnIsVisibleChanged; + } + + private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) + { + if (this.loaded || !this.IsVisible) + return; + this.loaded = true; + if (this.DataContext is PluginStoreViewModel vm) + { + _ = vm.LoadCommand.ExecuteAsync(null); + } + } +} + +/// +/// null でない場合に true を返すコンバーター +/// +[ValueConversion(typeof(object), typeof(bool))] +public sealed class NotNullToBoolConverter : IValueConverter +{ + public static NotNullToBoolConverter Default { get; } = new(); + + public object Convert(object? value, Type targetType, object parameter, CultureInfo culture) + => value is not null; + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + => throw new NotSupportedException(); +} + +/// +/// null でない場合に Visible を返すコンバーター +/// +[ValueConversion(typeof(object), typeof(Visibility))] +public sealed class NotNullToVisibilityConverter : IValueConverter +{ + public static NotNullToVisibilityConverter Default { get; } = new(); + + public object Convert(object? value, Type targetType, object parameter, CultureInfo culture) + => value is not null ? Visibility.Visible : Visibility.Collapsed; + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + => throw new NotSupportedException(); +} + +/// +/// bool を反転するコンバーター(Visibility対応) +/// +[ValueConversion(typeof(bool), typeof(object))] +public sealed class InverseBoolConverter : IValueConverter +{ + public static InverseBoolConverter Default { get; } = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var inverseBool = value is bool b && !b; + if (parameter is string p && p == "Visibility") + return inverseBool ? Visibility.Visible : Visibility.Collapsed; + return inverseBool; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + => value is bool b && !b; +} diff --git a/WindowTranslator/Modules/PluginStore/PluginStoreViewModel.cs b/WindowTranslator/Modules/PluginStore/PluginStoreViewModel.cs new file mode 100644 index 00000000..527e005a --- /dev/null +++ b/WindowTranslator/Modules/PluginStore/PluginStoreViewModel.cs @@ -0,0 +1,264 @@ +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Microsoft.Extensions.Logging; +using WindowTranslator.Properties; +using Wpf.Ui; +using Wpf.Ui.Extensions; + +namespace WindowTranslator.Modules.PluginStore; + +/// +/// プラグインストアのViewModel +/// +public partial class PluginStoreViewModel : ObservableObject +{ + private readonly NuGetPluginService nugetService; + private readonly ILogger logger; + private readonly IContentDialogService dialogService; + private readonly ISnackbarService snackbarService; + + [ObservableProperty] + private bool isLoading; + + [ObservableProperty] + private string? errorMessage; + + [ObservableProperty] + private PluginPackageViewModel? selectedPackage; + + public ObservableCollection Packages { get; } = []; + + public PluginStoreViewModel( + NuGetPluginService nugetService, + ILogger logger, + IContentDialogService dialogService, + ISnackbarService snackbarService) + { + this.nugetService = nugetService; + this.logger = logger; + this.dialogService = dialogService; + this.snackbarService = snackbarService; + } + + /// + /// プラグイン一覧を読み込みます。 + /// + [RelayCommand] + public async Task LoadAsync(CancellationToken cancellationToken = default) + { + if (this.IsLoading) + return; + + this.IsLoading = true; + this.ErrorMessage = null; + + try + { + var installed = await this.nugetService.GetInstalledPackagesAsync(cancellationToken).ConfigureAwait(true); + var installedDict = installed.ToDictionary(p => p.Id, StringComparer.OrdinalIgnoreCase); + + var packages = await this.nugetService.SearchPackagesAsync(cancellationToken).ConfigureAwait(true); + this.logger.LogInformation("NuGetから{Count}件のプラグインパッケージを取得しました。", packages.Count); + + this.Packages.Clear(); + foreach (var pkg in packages) + { + installedDict.TryGetValue(pkg.Id, out var installedInfo); + var isInstalled = installedInfo is not null; + var installedVersion = installedInfo?.Version; + var isUpdateAvailable = isInstalled + && installedVersion is not null + && IsNewerVersion(pkg.Version, installedVersion); + + this.Packages.Add(new PluginPackageViewModel(pkg, isInstalled, installedVersion, isUpdateAvailable)); + } + + // インストール済みだがNuGetに見つからないパッケージも表示 + foreach (var inst in installed) + { + if (!this.Packages.Any(p => p.Id.Equals(inst.Id, StringComparison.OrdinalIgnoreCase))) + { + this.Packages.Add(new PluginPackageViewModel( + new NuGetPackageInfo(inst.Id, inst.Version, inst.Id, string.Empty, string.Empty, null, null), + isInstalled: true, + installedVersion: inst.Version, + isUpdateAvailable: false)); + } + } + } + catch (OperationCanceledException) + { + // キャンセルは正常 + } + catch (Exception ex) + { + this.logger.LogError(ex, "NuGet検索に失敗しました。"); + this.ErrorMessage = Resources.NuGetSearchFailed; + } + finally + { + this.IsLoading = false; + } + } + + /// + /// プラグインをインストールまたは更新します。 + /// + [RelayCommand] + public async Task InstallAsync(PluginPackageViewModel package) + { + package.IsInstalling = true; + try + { + this.logger.LogInformation("プラグインのインストール開始: {PackageId} {Version}", package.Id, package.LatestVersion); + var progress = new Progress(v => package.InstallProgress = v); + await this.nugetService.InstallPackageAsync(package.Id, package.LatestVersion, progress).ConfigureAwait(true); + + package.IsInstalled = true; + package.InstalledVersion = package.LatestVersion; + package.IsUpdateAvailable = false; + package.InstallProgress = 0; + + this.logger.LogInformation("プラグインのインストール完了: {PackageId}", package.Id); + + // 再起動が必要な旨を表示 + await this.dialogService.ShowSimpleDialogAsync(new() + { + Title = Resources.PluginInstallSuccess, + Content = Resources.RestartRequired, + CloseButtonText = Resources.Close, + }).ConfigureAwait(true); + } + catch (OperationCanceledException) + { + // キャンセルは正常 + } + catch (Exception ex) + { + this.logger.LogError(ex, "プラグインのインストールに失敗しました: {PackageId}", package.Id); + await this.dialogService.ShowAlertAsync( + Resources.PluginInstallFailed, + ex.Message, + Resources.Close).ConfigureAwait(true); + } + finally + { + package.IsInstalling = false; + } + } + + /// + /// プラグインをアンインストールします。 + /// + [RelayCommand] + public async Task UninstallAsync(PluginPackageViewModel package) + { + var result = await this.dialogService.ShowSimpleDialogAsync(new() + { + Title = Resources.Uninstall, + Content = string.Format(Resources.UninstallConfirm, package.Title), + PrimaryButtonText = Resources.Uninstall, + CloseButtonText = Resources.Cancel, + }).ConfigureAwait(true); + + if (result != Wpf.Ui.Controls.ContentDialogResult.Primary) + return; + + try + { + await this.nugetService.UninstallPackageAsync(package.Id).ConfigureAwait(true); + package.IsInstalled = false; + package.InstalledVersion = null; + package.IsUpdateAvailable = false; + + await this.dialogService.ShowSimpleDialogAsync(new() + { + Title = Resources.Uninstall, + Content = Resources.RestartRequired, + CloseButtonText = Resources.Close, + }).ConfigureAwait(true); + } + catch (Exception ex) + { + this.logger.LogError(ex, "プラグインのアンインストールに失敗しました: {PackageId}", package.Id); + await this.dialogService.ShowAlertAsync( + Resources.Uninstall, + ex.Message, + Resources.Close).ConfigureAwait(true); + } + } + + private static bool IsNewerVersion(string latestVersion, string installedVersion) + { + try + { + return Version.Parse(latestVersion) > Version.Parse(installedVersion); + } + catch + { + return string.Compare(latestVersion, installedVersion, StringComparison.OrdinalIgnoreCase) > 0; + } + } +} + +/// +/// プラグインパッケージの表示モデル +/// +public partial class PluginPackageViewModel : ObservableObject +{ + public string Id { get; } + public string Title { get; } + public string Description { get; } + public string Authors { get; } + public string LatestVersion { get; } + public string? ProjectUrl { get; } + public string? LicenseUrl { get; } + + [ObservableProperty] + private bool isInstalled; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(StatusText))] + private string? installedVersion; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(StatusText))] + private bool isUpdateAvailable; + + [ObservableProperty] + private bool isInstalling; + + [ObservableProperty] + private double installProgress; + + public string StatusText + { + get + { + if (this.IsUpdateAvailable && this.InstalledVersion is not null) + return string.Format(Properties.Resources.UpdateAvailableVersion, this.InstalledVersion, this.LatestVersion); + if (this.IsInstalled && this.InstalledVersion is not null) + return string.Format(Properties.Resources.InstalledVersion, this.InstalledVersion); + return string.Empty; + } + } + + public PluginPackageViewModel( + NuGetPackageInfo info, + bool isInstalled, + string? installedVersion, + bool isUpdateAvailable) + { + this.Id = info.Id; + this.Title = info.Title; + this.Description = info.Description; + this.Authors = info.Authors; + this.LatestVersion = info.Version; + this.ProjectUrl = info.ProjectUrl; + this.LicenseUrl = info.LicenseUrl; + this.isInstalled = isInstalled; + this.installedVersion = installedVersion; + this.isUpdateAvailable = isUpdateAvailable; + } +} diff --git a/WindowTranslator/Modules/Settings/AllSettingsDialog.xaml b/WindowTranslator/Modules/Settings/AllSettingsDialog.xaml index 890a733b..8c2eb942 100644 --- a/WindowTranslator/Modules/Settings/AllSettingsDialog.xaml +++ b/WindowTranslator/Modules/Settings/AllSettingsDialog.xaml @@ -6,6 +6,7 @@ xmlns:data="clr-namespace:WindowTranslator.Data" xmlns:local="clr-namespace:WindowTranslator.Modules.Settings" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:pluginStore="clr-namespace:WindowTranslator.Modules.PluginStore" xmlns:properties="clr-namespace:WindowTranslator.Properties" xmlns:pt="http://propertytools.org/wpf" xmlns:root="clr-namespace:WindowTranslator" @@ -399,6 +400,9 @@ + + + diff --git a/WindowTranslator/Modules/Settings/AllSettingsViewModel.cs b/WindowTranslator/Modules/Settings/AllSettingsViewModel.cs index 46d0641b..b7ecd468 100644 --- a/WindowTranslator/Modules/Settings/AllSettingsViewModel.cs +++ b/WindowTranslator/Modules/Settings/AllSettingsViewModel.cs @@ -19,6 +19,7 @@ using WindowTranslator.ComponentModel; using WindowTranslator.Extensions; using WindowTranslator.Modules.Main; +using WindowTranslator.Modules.PluginStore; using WindowTranslator.Properties; using WindowTranslator.Stores; using Wpf.Ui; @@ -103,6 +104,8 @@ sealed partial class AllSettingsViewModel : ObservableObject, IDisposable public bool IsVisibleReviewButton => this.reviewRequestService.CanOpenReview; + public PluginStoreViewModel PluginStore { get; } + public AllSettingsViewModel( [Inject] PluginProvider provider, [Inject] IOptionsSnapshot options, @@ -116,6 +119,7 @@ public AllSettingsViewModel( [Inject] IEnumerable validators, [Inject] IMainWindowModule mainWindowModule, [Inject] ILogger logger, + [Inject] PluginStoreViewModel pluginStoreViewModel, string target, bool? applyMode = null) { @@ -158,6 +162,7 @@ public AllSettingsViewModel( this.logger = logger; this.target = target; this.rootConfig = config as IConfigurationRoot; + this.PluginStore = pluginStoreViewModel; this.updateChecker.UpdateAvailable += UpdateChecker_UpdateAvailable; SetUpUpdateInfo(); this.isStartup = GetIsStartup(); diff --git a/WindowTranslator/Program.cs b/WindowTranslator/Program.cs index f2df1337..7296b8be 100644 --- a/WindowTranslator/Program.cs +++ b/WindowTranslator/Program.cs @@ -26,6 +26,7 @@ using WindowTranslator.Modules.ErrorReport; using WindowTranslator.Modules.LogView; using WindowTranslator.Modules.Main; +using WindowTranslator.Modules.PluginStore; using WindowTranslator.Modules.Settings; using WindowTranslator.Modules.Startup; using WindowTranslator.Modules.Validate; @@ -161,6 +162,8 @@ builder.Services.AddPresentation(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddTransient(); builder.Services.Configure(builder.Configuration, op => op.ErrorOnUnknownConfiguration = false); builder.Services.Configure(builder.Configuration.GetSection(nameof(UserSettings.Common))); builder.Services.AddTransient(typeof(IConfigureNamedOptions<>), typeof(ConfigurePluginParam<>)); @@ -185,6 +188,9 @@ e.Window.Activate(); }; +// 起動時にペンディング削除を処理する +app.Services.GetRequiredService().ProcessPendingDeletions(); + if (SentrySdk.IsEnabled) { app.Logger.LogInformation("Sentry is enabled."); diff --git a/WindowTranslator/Properties/Resources.Designer.cs b/WindowTranslator/Properties/Resources.Designer.cs index 37d4cd4d..6ff2bf59 100644 --- a/WindowTranslator/Properties/Resources.Designer.cs +++ b/WindowTranslator/Properties/Resources.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // @@ -28,7 +28,7 @@ namespace WindowTranslator.Properties; /// -/// [JCYꂽȂǂ邽߂́AɌ^w肳ꂽ\[X NXłB +/// ローカライズされた文字列などを検索するための、厳密に型指定されたリソース クラスです。 /// // This class was auto-generated by a text template. // To add or remove a member, edit your .ResX file. @@ -45,15 +45,15 @@ internal Resources() { } /// - /// ̃NXŎgpĂLbVꂽ ResourceManager CX^XԂ܂B + /// このクラスで使用されているキャッシュされた ResourceManager インスタンスを返します。 /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] public static global::System.Resources.ResourceManager ResourceManager => resourceMan ??= new CustomResourceManager("WindowTranslator.Properties.Resources", Assembly.GetExecutingAssembly()); /// - /// ׂĂɂ‚āÃ݂Xbh CurrentUICulture vpeBI[o[Ch܂ - /// ݂̃Xbh CurrentUICulture vpeBI[o[Ch܂B + /// すべてについて、現在のスレッドの CurrentUICulture プロパティをオーバーライドします + /// 現在のスレッドの CurrentUICulture プロパティをオーバーライドします。 /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] public static global::System.Globalization.CultureInfo Culture @@ -63,542 +63,627 @@ internal Resources() { } /// - /// "̃Avɂ‚" ɗގĂ郍[JCYꂽ܂B + /// "このアプリについて" に類似しているローカライズされた文字列を検索します。 /// public static string About => ResourceManager.GetString("About", resourceCulture) ?? string.Empty; /// - /// "A" ɗގĂ郍[JCYꂽ܂B + /// "連絡先" に類似しているローカライズされた文字列を検索します。 /// public static string Address => ResourceManager.GetString("Address", resourceCulture) ?? string.Empty; /// - /// "Av" ɗގĂ郍[JCYꂽ܂B + /// "アプリ情報" に類似しているローカライズされた文字列を検索します。 /// public static string Application => ResourceManager.GetString("Application", resourceCulture) ?? string.Empty; /// - /// "Kp" ɗގĂ郍[JCYꂽ܂B + /// "適用" に類似しているローカライズされた文字列を検索します。 /// public static string Apply => ResourceManager.GetString("Apply", resourceCulture) ?? string.Empty; /// - /// "A^b`" ɗގĂ郍[JCYꂽ܂B + /// "アタッチ" に類似しているローカライズされた文字列を検索します。 /// public static string Attach => ResourceManager.GetString("Attach", resourceCulture) ?? string.Empty; /// - /// "A^b`" ɗގĂ郍[JCYꂽ܂B + /// "アタッチ中" に類似しているローカライズされた文字列を検索します。 /// public static string Attaching => ResourceManager.GetString("Attaching", resourceCulture) ?? string.Empty; /// - /// "XN[" ɗގĂ郍[JCYꂽ܂B + /// "自動スクロール" に類似しているローカライズされた文字列を検索します。 /// public static string AutoScroll => ResourceManager.GetString("AutoScroll", resourceCulture) ?? string.Empty; /// - /// "N" ɗގĂ郍[JCYꂽ܂B + /// "自動起動" に類似しているローカライズされた文字列を検索します。 /// public static string AutoStart => ResourceManager.GetString("AutoStart", resourceCulture) ?? string.Empty; /// - /// "PCNɎN" ɗގĂ郍[JCYꂽ܂B + /// "PC起動時に自動起動" に類似しているローカライズされた文字列を検索します。 /// public static string AutoStartWithPC => ResourceManager.GetString("AutoStartWithPC", resourceCulture) ?? string.Empty; /// - /// "|Ώ" ɗގĂ郍[JCYꂽ܂B + /// "自動翻訳対象" に類似しているローカライズされた文字列を検索します。 /// public static string AutoTargets => ResourceManager.GetString("AutoTargets", resourceCulture) ?? string.Empty; /// - /// "rh" ɗގĂ郍[JCYꂽ܂B + /// "ビルド日時" に類似しているローカライズされた文字列を検索します。 /// public static string BuildDate => ResourceManager.GetString("BuildDate", resourceCulture) ?? string.Empty; /// - /// "LbVW[" ɗގĂ郍[JCYꂽ܂B + /// "キャッシュモジュール" に類似しているローカライズされた文字列を検索します。 /// public static string CacheModule => ResourceManager.GetString("CacheModule", resourceCulture) ?? string.Empty; /// - /// "LbVݒ" ɗގĂ郍[JCYꂽ܂B + /// "キャッシュ設定" に類似しているローカライズされた文字列を検索します。 /// public static string CacheParam => ResourceManager.GetString("CacheParam", resourceCulture) ?? string.Empty; /// - /// "LZ" ɗގĂ郍[JCYꂽ܂B + /// "キャンセル" に類似しているローカライズされた文字列を検索します。 /// public static string Cancel => ResourceManager.GetString("Cancel", resourceCulture) ?? string.Empty; /// - /// "Lv`[EBhE" ɗގĂ郍[JCYꂽ܂B + /// "キャプチャーウィンドウ" に類似しているローカライズされた文字列を検索します。 /// public static string Capture => ResourceManager.GetString("Capture", resourceCulture) ?? string.Empty; /// - /// "Vo[W̃`FbN" ɗގĂ郍[JCYꂽ܂B + /// "新しいバージョンのチェック" に類似しているローカライズされた文字列を検索します。 /// public static string CheckNewVersion => ResourceManager.GetString("CheckNewVersion", resourceCulture) ?? string.Empty; /// - /// "XVe̊mF" ɗގĂ郍[JCYꂽ܂B + /// "更新内容の確認" に類似しているローカライズされた文字列を検索します。 /// public static string CheckUpdateNotes => ResourceManager.GetString("CheckUpdateNotes", resourceCulture) ?? string.Empty; /// - /// "NA" ɗގĂ郍[JCYꂽ܂B + /// "クリア" に類似しているローカライズされた文字列を検索します。 /// public static string Clear => ResourceManager.GetString("Clear", resourceCulture) ?? string.Empty; /// - /// "‚" ɗގĂ郍[JCYꂽ܂B + /// "閉じる" に類似しているローカライズされた文字列を検索します。 /// public static string Close => ResourceManager.GetString("Close", resourceCulture) ?? string.Empty; /// - /// "mF" ɗގĂ郍[JCYꂽ܂B + /// "確認" に類似しているローカライズされた文字列を検索します。 /// public static string Confirm => ResourceManager.GetString("Confirm", resourceCulture) ?? string.Empty; /// - /// "Rs[܂" ɗގĂ郍[JCYꂽ܂B + /// "コピーしました" に類似しているローカライズされた文字列を検索します。 /// public static string Copied => ResourceManager.GetString("Copied", resourceCulture) ?? string.Empty; /// - /// "Rs[" ɗގĂ郍[JCYꂽ܂B + /// "情報をコピー" に類似しているローカライズされた文字列を検索します。 /// public static string Copy => ResourceManager.GetString("Copy", resourceCulture) ?? string.Empty; /// - /// "ftHgݒ" ɗގĂ郍[JCYꂽ܂B + /// "デフォルト設定" に類似しているローカライズされた文字列を検索します。 /// public static string DefaultSetting => ResourceManager.GetString("DefaultSetting", resourceCulture) ?? string.Empty; /// - /// "f^b`" ɗގĂ郍[JCYꂽ܂B + /// "デタッチ" に類似しているローカライズされた文字列を検索します。 /// public static string Detach => ResourceManager.GetString("Detach", resourceCulture) ?? string.Empty; /// - /// "Zp" ɗގĂ郍[JCYꂽ܂B + /// "技術情報" に類似しているローカライズされた文字列を検索します。 /// public static string Develop => ResourceManager.GetString("Develop", resourceCulture) ?? string.Empty; /// - /// "J" ɗގĂ郍[JCYꂽ܂B + /// "開発者" に類似しているローカライズされた文字列を検索します。 /// public static string DevelopedBy => ResourceManager.GetString("DevelopedBy", resourceCulture) ?? string.Empty; /// - /// "ACR\" ɗގĂ郍[JCYꂽ܂B + /// "処理中アイコンを表示する" に類似しているローカライズされた文字列を検索します。 /// public static string DisplayBusy => ResourceManager.GetString("DisplayBusy", resourceCulture) ?? string.Empty; /// - /// "\@" ɗގĂ郍[JCYꂽ܂B + /// "表示方法" に類似しているローカライズされた文字列を検索します。 /// public static string DisplayMethod => ResourceManager.GetString("DisplayMethod", resourceCulture) ?? string.Empty; /// - /// "I" ɗގĂ郍[JCYꂽ܂B + /// "終了" に類似しているローカライズされた文字列を検索します。 /// public static string Exit => ResourceManager.GetString("Exit", resourceCulture) ?? string.Empty; /// - /// "GNX|[g" ɗގĂ郍[JCYꂽ܂B + /// "エクスポート" に類似しているローカライズされた文字列を検索します。 /// public static string Export => ResourceManager.GetString("Export", resourceCulture) ?? string.Empty; /// - /// "ÕGNX|[g" ɗގĂ郍[JCYꂽ܂B + /// "ログのエクスポート" に類似しているローカライズされた文字列を検索します。 /// public static string ExportLogs => ResourceManager.GetString("ExportLogs", resourceCulture) ?? string.Empty; /// - /// "GNX|[gs" ɗގĂ郍[JCYꂽ܂B + /// "エクスポート失敗" に類似しているローカライズされた文字列を検索します。 /// public static string ExportLogsFailed => ResourceManager.GetString("ExportLogsFailed", resourceCulture) ?? string.Empty; /// - /// "eLXgt@C" ɗގĂ郍[JCYꂽ܂B + /// "テキストファイル" に類似しているローカライズされた文字列を検索します。 /// public static string ExportLogsFilterText => ResourceManager.GetString("ExportLogsFilterText", resourceCulture) ?? string.Empty; /// - /// "GNX|[g" ɗގĂ郍[JCYꂽ܂B + /// "エクスポート完了" に類似しているローカライズされた文字列を検索します。 /// public static string ExportLogsSuccess => ResourceManager.GetString("ExportLogsSuccess", resourceCulture) ?? string.Empty; /// - /// "O`{0}`ɃGNX|[g܂B" ɗގĂ郍[JCYꂽ܂B + /// "ログを`{0}`にエクスポートしました。" に類似しているローカライズされた文字列を検索します。 /// public static string ExportLogsSuccessDetail => ResourceManager.GetString("ExportLogsSuccessDetail", resourceCulture) ?? string.Empty; /// - /// "ݒ̓KpɎs܂B" ɗގĂ郍[JCYꂽ܂B + /// "設定の適用に失敗しました。" に類似しているローカライズされた文字列を検索します。 /// public static string FaildApplySettings => ResourceManager.GetString("FaildApplySettings", resourceCulture) ?? string.Empty; /// - /// "OCRɎs܂" ɗގĂ郍[JCYꂽ܂B + /// "OCRに失敗しました" に類似しているローカライズされた文字列を検索します。 /// public static string FaildOcr => ResourceManager.GetString("FaildOcr", resourceCulture) ?? string.Empty; /// - /// "EBhE̖ߍ݂Ɏs܂B" ɗގĂ郍[JCYꂽ܂B + /// "ウィンドウの埋め込みに失敗しました。" に類似しているローカライズされた文字列を検索します。 /// public static string FaildOverlay => ResourceManager.GetString("FaildOverlay", resourceCulture) ?? string.Empty; /// - /// "|Ɏs܂" ɗގĂ郍[JCYꂽ܂B + /// "翻訳に失敗しました" に類似しているローカライズされた文字列を検索します。 /// public static string FaildTranslate => ResourceManager.GetString("FaildTranslate", resourceCulture) ?? string.Empty; /// - /// "߂eLXg臒l" ɗގĂ郍[JCYꂽ܂B + /// "近いテキストの閾値" に類似しているローカライズされた文字列を検索します。 /// public static string FuzzyMatchThreshold => ResourceManager.GetString("FuzzyMatchThreshold", resourceCulture) ?? string.Empty; /// - /// "Sʐݒ" ɗގĂ郍[JCYꂽ܂B + /// "全般設定" に類似しているローカライズされた文字列を検索します。 /// public static string GeneralSettings => ResourceManager.GetString("GeneralSettings", resourceCulture) ?? string.Empty; /// - /// "Abvf[g܂: {0}" ɗގĂ郍[JCYꂽ܂B + /// "アップデートがあります: {0}" に類似しているローカライズされた文字列を検索します。 /// public static string HasUpdate => ResourceManager.GetString("HasUpdate", resourceCulture) ?? string.Empty; /// - /// "ĂԂ" ɗގĂ郍[JCYꂽ܂B + /// "押している間だけ" に類似しているローカライズされた文字列を検索します。 /// public static string Hold => ResourceManager.GetString("Hold", resourceCulture) ?? string.Empty; /// - /// "LbV" ɗގĂ郍[JCYꂽ܂B + /// "メモリ内キャッシュ" に類似しているローカライズされた文字列を検索します。 /// public static string InMemoryCache => ResourceManager.GetString("InMemoryCache", resourceCulture) ?? string.Empty; /// - /// "Vo[W: {0} ̃CXg[" ɗގĂ郍[JCYꂽ܂B + /// "インストール" に類似しているローカライズされた文字列を検索します。 + /// + public static string Install => ResourceManager.GetString("Install", resourceCulture) ?? string.Empty; + + /// + /// "インストール済み" に類似しているローカライズされた文字列を検索します。 + /// + public static string Installed => ResourceManager.GetString("Installed", resourceCulture) ?? string.Empty; + + /// + /// "インストール済み: {0}" に類似しているローカライズされた文字列を検索します。 + /// + public static string InstalledVersion => ResourceManager.GetString("InstalledVersion", resourceCulture) ?? string.Empty; + + /// + /// "インストール済みバージョン" に類似しているローカライズされた文字列を検索します。 + /// + public static string InstalledVersionLabel => ResourceManager.GetString("InstalledVersionLabel", resourceCulture) ?? string.Empty; + + /// + /// "新しいバージョン: {0} のインストール" に類似しているローカライズされた文字列を検索します。 /// public static string InstallNewVersion => ResourceManager.GetString("InstallNewVersion", resourceCulture) ?? string.Empty; /// - /// "{0}: ݒ茟؃G[" ɗގĂ郍[JCYꂽ܂B + /// "{0}: 設定検証エラー" に類似しているローカライズされた文字列を検索します。 /// public static string InvalidSettings => ResourceManager.GetString("InvalidSettings", resourceCulture) ?? string.Empty; /// - /// ":tired-face: **̂܂܎sĂ삵Ȃ”\ł**&#13;&a..." ɗގĂ郍[JCYꂽ܂B + /// ":tired-face: **そのまま実行しても動作しない可能性が高いです**&#10;**..." に類似しているローカライズされた文字列を検索します。 /// public static string InvalidSettingsContent => ResourceManager.GetString("InvalidSettingsContent", resourceCulture) ?? string.Empty; /// - /// "x|ΏۂɑIvZXNƂɎIɖ|󂷂" ɗގĂ郍[JCYꂽ܂B + /// "一度翻訳対象に選択したプロセスが起動したときに自動的に翻訳する" に類似しているローカライズされた文字列を検索します。 /// public static string IsEnableAutoTarget => ResourceManager.GetString("IsEnableAutoTarget", resourceCulture) ?? string.Empty; /// - /// "I[o[C\Lv`[”\ɂ" ɗގĂ郍[JCYꂽ܂B + /// "オーバーレイ表示をキャプチャー可能にする" に類似しているローカライズされた文字列を検索します。 /// public static string IsEnableCaptureOverlay => ResourceManager.GetString("IsEnableCaptureOverlay", resourceCulture) ?? string.Empty; /// - /// "ŐVo[WpłB" ɗގĂ郍[JCYꂽ܂B + /// "最新バージョンをご利用中です。" に類似しているローカライズされた文字列を検索します。 /// public static string IsLatest => ResourceManager.GetString("IsLatest", resourceCulture) ?? string.Empty; /// - /// "I[oCLɂ^C~Ôݖ|󂷂" ɗގĂ郍[JCYꂽ܂B + /// "オーバレイを有効にしたタイミングのみ翻訳する" に類似しているローカライズされた文字列を検索します。 /// public static string IsOneShotMode => ResourceManager.GetString("IsOneShotMode", resourceCulture) ?? string.Empty; /// - /// "}EX|C^[ʒũeLXĝ݃I[oC|\" ɗގĂ郍[JCYꂽ܂B + /// "マウスポインター位置のテキストのみオーバレイ翻訳を表示する" に類似しているローカライズされた文字列を検索します。 /// public static string IsOverlayPointSwap => ResourceManager.GetString("IsOverlayPointSwap", resourceCulture) ?? string.Empty; /// - /// "ݒ" ɗގĂ郍[JCYꂽ܂B + /// "言語設定" に類似しているローカライズされた文字列を検索します。 /// public static string Language => ResourceManager.GetString("Language", resourceCulture) ?? string.Empty; /// - /// "CZX" ɗގĂ郍[JCYꂽ܂B + /// "最新バージョン" に類似しているローカライズされた文字列を検索します。 + /// + public static string LatestVersion => ResourceManager.GetString("LatestVersion", resourceCulture) ?? string.Empty; + + /// + /// "ライセンス" に類似しているローカライズされた文字列を検索します。 /// public static string License => ResourceManager.GetString("License", resourceCulture) ?? string.Empty; /// - /// "[Jt@CLbV" ɗގĂ郍[JCYꂽ܂B + /// "ライセンス情報" に類似しているローカライズされた文字列を検索します。 + /// + public static string LicenseUrl => ResourceManager.GetString("LicenseUrl", resourceCulture) ?? string.Empty; + + /// + /// "ローカルファイルキャッシュ" に類似しているローカライズされた文字列を検索します。 /// public static string LocalCache => ResourceManager.GetString("LocalCache", resourceCulture) ?? string.Empty; /// - /// "O" ɗގĂ郍[JCYꂽ܂B + /// "ログ" に類似しているローカライズされた文字列を検索します。 /// public static string Log => ResourceManager.GetString("Log", resourceCulture) ?? string.Empty; /// - /// "̑" ɗގĂ郍[JCYꂽ܂B + /// "その他" に類似しているローカライズされた文字列を検索します。 /// public static string Misc => ResourceManager.GetString("Misc", resourceCulture) ?? string.Empty; /// - /// "łWindowTranslatorNł" ɗގĂ郍[JCYꂽ܂B + /// "すでにWindowTranslatorが起動中です" に類似しているローカライズされた文字列を検索します。 /// public static string MutexError => ResourceManager.GetString("MutexError", resourceCulture) ?? string.Empty; /// - /// "Vo[W: {0} [X܂" ɗގĂ郍[JCYꂽ܂B + /// "新しいバージョン: {0} がリリースされました" に類似しているローカライズされた文字列を検索します。 /// public static string NewVersionAvailable => ResourceManager.GetString("NewVersionAvailable", resourceCulture) ?? string.Empty; /// - /// "LbVȂ" ɗގĂ郍[JCYꂽ܂B + /// "キャッシュしない" に類似しているローカライズされた文字列を検索します。 /// public static string NoCache => ResourceManager.GetString("NoCache", resourceCulture) ?? string.Empty; /// - /// "|󂵂Ȃ" ɗގĂ郍[JCYꂽ܂B + /// "翻訳しない" に類似しているローカライズされた文字列を検索します。 /// public static string NoTranslateModule => ResourceManager.GetString("NoTranslateModule", resourceCulture) ?? string.Empty; /// - /// "{0}OCR@\g܂BΏۂ̌@\CXg[Ă" ɗގĂ郍[JCYꂽ܂B + /// "NuGetからのプラグイン一覧の取得に失敗しました。ネットワーク接続を確認してください。" に類似しているローカライズされた文字列を検索します。 + /// + public static string NuGetSearchFailed => ResourceManager.GetString("NuGetSearchFailed", resourceCulture) ?? string.Empty; + + /// + /// "{0}のOCR機能が使えません。対象の言語機能をインストールしてください" に類似しているローカライズされた文字列を検索します。 /// public static string OcrLanguageNotAvailable => ResourceManager.GetString("OcrLanguageNotAvailable", resourceCulture) ?? string.Empty; /// - /// "FW[" ɗގĂ郍[JCYꂽ܂B + /// "認識モジュール" に類似しているローカライズされた文字列を検索します。 /// public static string OcrModule => ResourceManager.GetString("OcrModule", resourceCulture) ?? string.Empty; /// - /// "OK" ɗގĂ郍[JCYꂽ܂B + /// "OK" に類似しているローカライズされた文字列を検索します。 /// public static string OK => ResourceManager.GetString("OK", resourceCulture) ?? string.Empty; /// - /// "ڍ׏̊mF" ɗގĂ郍[JCYꂽ܂B + /// "詳細情報の確認" に類似しているローカライズされた文字列を検索します。 /// public static string OpenChangelogCommand => ResourceManager.GetString("OpenChangelogCommand", resourceCulture) ?? string.Empty; /// - /// "T[hp[eB[CZX̊mF" ɗގĂ郍[JCYꂽ܂B + /// "サードパーティーライセンスの確認" に類似しているローカライズされた文字列を検索します。 /// public static string OpenThirdPartyLicensesCommand => ResourceManager.GetString("OpenThirdPartyLicensesCommand", resourceCulture) ?? string.Empty; /// - /// "I[oC" ɗގĂ郍[JCYꂽ܂B + /// "オーバレイ" に類似しているローカライズされた文字列を検索します。 /// public static string Overlay => ResourceManager.GetString("Overlay", resourceCulture) ?? string.Empty; /// - /// "I[o[Cwi̕sx" ɗގĂ郍[JCYꂽ܂B + /// "オーバーレイ背景の不透明度" に類似しているローカライズされた文字列を検索します。 /// public static string OverlayOpacity => ResourceManager.GetString("OverlayOpacity", resourceCulture) ?? string.Empty; /// - /// "I[o[C؂ւ" ɗގĂ郍[JCYꂽ܂B + /// "オーバーレイ切り替え" に類似しているローカライズされた文字列を検索します。 /// public static string OverlayShortcut => ResourceManager.GetString("OverlayShortcut", resourceCulture) ?? string.Empty; /// - /// "I[o[C\̐؂ւ" ɗގĂ郍[JCYꂽ܂B + /// "オーバーレイ表示の切り替え" に類似しているローカライズされた文字列を検索します。 /// public static string OverlaySwitch => ResourceManager.GetString("OverlaySwitch", resourceCulture) ?? string.Empty; /// - /// "vOCݒ" ɗގĂ郍[JCYꂽ܂B + /// "プラグイン設定" に類似しているローカライズされた文字列を検索します。 /// public static string Plugin => ResourceManager.GetString("Plugin", resourceCulture) ?? string.Empty; /// - /// "Jy[W" ɗގĂ郍[JCYꂽ܂B + /// "インストール失敗" に類似しているローカライズされた文字列を検索します。 + /// + public static string PluginInstallFailed => ResourceManager.GetString("PluginInstallFailed", resourceCulture) ?? string.Empty; + + /// + /// "インストール完了" に類似しているローカライズされた文字列を検索します。 + /// + public static string PluginInstallSuccess => ResourceManager.GetString("PluginInstallSuccess", resourceCulture) ?? string.Empty; + + /// + /// "プラグインストア" に類似しているローカライズされた文字列を検索します。 + /// + public static string PluginStore => ResourceManager.GetString("PluginStore", resourceCulture) ?? string.Empty; + + /// + /// "プロジェクトページ" に類似しているローカライズされた文字列を検索します。 + /// + public static string ProjectUrl => ResourceManager.GetString("ProjectUrl", resourceCulture) ?? string.Empty; + + /// + /// "公開ページ" に類似しているローカライズされた文字列を検索します。 /// public static string PublishPage => ResourceManager.GetString("PublishPage", resourceCulture) ?? string.Empty; /// - /// "{0}Nɓo^܂B" ɗގĂ郍[JCYꂽ܂B + /// "{0}を自動起動に登録しました。" に類似しているローカライズされた文字列を検索します。 /// public static string RegisterAutoStart => ResourceManager.GetString("RegisterAutoStart", resourceCulture) ?? string.Empty; /// - /// "" ɗގĂ郍[JCYꂽ܂B + /// "プラグインの変更を適用するには、WindowTranslatorを再起動してください。" に類似しているローカライズされた文字列を検索します。 + /// + public static string RestartRequired => ResourceManager.GetString("RestartRequired", resourceCulture) ?? string.Empty; + + /// + /// "後で" に類似しているローカライズされた文字列を検索します。 /// public static string ReviewLater => ResourceManager.GetString("ReviewLater", resourceCulture) ?? string.Empty; /// - /// "xƕ\Ȃ" ɗގĂ郍[JCYꂽ܂B + /// "二度と表示しない" に類似しているローカライズされた文字列を検索します。 /// public static string ReviewNeverShowAgain => ResourceManager.GetString("ReviewNeverShowAgain", resourceCulture) ?? string.Empty; /// - /// "r[̂肢" ɗގĂ郍[JCYꂽ܂B + /// "レビューのお願い" に類似しているローカライズされた文字列を検索します。 /// public static string ReviewRequest => ResourceManager.GetString("ReviewRequest", resourceCulture) ?? string.Empty; /// - /// "WindowTranslatorp肪Ƃ܂BMicrosoft Store..." ɗގĂ郍[JCYꂽ܂B + /// "WindowTranslatorをご利用いただきありがとうございます。Microsoft Store..." に類似しているローカライズされた文字列を検索します。 /// public static string ReviewRequestMessage => ResourceManager.GetString("ReviewRequestMessage", resourceCulture) ?? string.Empty; /// - /// "̂܂܎s" ɗގĂ郍[JCYꂽ܂B + /// "そのまま実行" に類似しているローカライズされた文字列を検索します。 /// public static string RunAsIs => ResourceManager.GetString("RunAsIs", resourceCulture) ?? string.Empty; /// - /// "|󌳌Ɩ|挾ꂪłBقȂ錾w肵ĂB" ɗގĂ郍[JCYꂽ܂B + /// "翻訳元言語と翻訳先言語が同一です。異なる言語を指定してください。" に類似しているローカライズされた文字列を検索します。 /// public static string SameSourceTargetLanguage => ResourceManager.GetString("SameSourceTargetLanguage", resourceCulture) ?? string.Empty; /// - /// "ۑĕ‚" ɗގĂ郍[JCYꂽ܂B + /// "保存して閉じる" に類似しているローカライズされた文字列を検索します。 /// public static string SaveAndClose => ResourceManager.GetString("SaveAndClose", resourceCulture) ?? string.Empty; /// - /// "WindowTranslatorȊÕEBhEIĂ" ɗގĂ郍[JCYꂽ܂B + /// "WindowTranslator以外のウィンドウを選択してください" に類似しているローカライズされた文字列を検索します。 /// public static string SelectOtherWindow => ResourceManager.GetString("SelectOtherWindow", resourceCulture) ?? string.Empty; /// - /// "G[|[gVXeɑM܂Bȉ̏񂪑M܂B&#13;&#1..." ɗގĂ郍[JCYꂽ܂B + /// "エラー情報をレポートシステムに送信します。以下の情報が送信されます。&#10;* アプリ情報..." に類似しているローカライズされた文字列を検索します。 /// public static string SendReportToolTip => ResourceManager.GetString("SendReportToolTip", resourceCulture) ?? string.Empty; /// - /// "𑗐M" ɗގĂ郍[JCYꂽ܂B + /// "情報を送信" に類似しているローカライズされた文字列を検索します。 /// public static string SendRerpot => ResourceManager.GetString("SendRerpot", resourceCulture) ?? string.Empty; /// - /// "M" ɗގĂ郍[JCYꂽ܂B + /// "送信完了" に類似しているローカライズされた文字列を検索します。 /// public static string Sent => ResourceManager.GetString("Sent", resourceCulture) ?? string.Empty; /// - /// ":tired-face: **̂܂ܕۑĂ삵܂B**&#13;&#10..." ɗގĂ郍[JCYꂽ܂B + /// ":tired-face: **このまま保存しても動作しません。**&#10;***&..." に類似しているローカライズされた文字列を検索します。 /// public static string SettingInvalidContent => ResourceManager.GetString("SettingInvalidContent", resourceCulture) ?? string.Empty; /// - /// "ݒ" ɗގĂ郍[JCYꂽ܂B + /// "設定" に類似しているローカライズされた文字列を検索します。 /// public static string Settings => ResourceManager.GetString("Settings", resourceCulture) ?? string.Empty; /// - /// "ݒ茟؃G[" ɗގĂ郍[JCYꂽ܂B + /// "設定検証エラー" に類似しているローカライズされた文字列を検索します。 /// public static string SettingsInvalid => ResourceManager.GetString("SettingsInvalid", resourceCulture) ?? string.Empty; /// - /// "S̐ݒ" ɗގĂ郍[JCYꂽ܂B + /// "全体設定" に類似しているローカライズされた文字列を検索します。 /// public static string SettingsViewModel => ResourceManager.GetString("SettingsViewModel", resourceCulture) ?? string.Empty; /// - /// "V[gJbg" ɗގĂ郍[JCYꂽ܂B + /// "ショートカット" に類似しているローカライズされた文字列を検索します。 /// public static string Shortcut => ResourceManager.GetString("Shortcut", resourceCulture) ?? string.Empty; /// - /// "|(F)" ɗގĂ郍[JCYꂽ܂B + /// "翻訳元(認識)言語" に類似しているローカライズされた文字列を検索します。 /// public static string Source => ResourceManager.GetString("Source", resourceCulture) ?? string.Empty; /// - /// "SteamŃQ[Mtg" ɗގĂ郍[JCYꂽ܂B + /// "Steamでゲームをギフト" に類似しているローカライズされた文字列を検索します。 /// public static string SteamWishlist => ResourceManager.GetString("SteamWishlist", resourceCulture) ?? string.Empty; /// - /// "M" ɗގĂ郍[JCYꂽ܂B + /// "送信" に類似しているローカライズされた文字列を検索します。 /// public static string Submit => ResourceManager.GetString("Submit", resourceCulture) ?? string.Empty; /// - /// "|(\)" ɗގĂ郍[JCYꂽ܂B + /// "翻訳先(表示)言語" に類似しているローカライズされた文字列を検索します。 /// public static string Target => ResourceManager.GetString("Target", resourceCulture) ?? string.Empty; /// - /// "|ΏۃvZX" ɗގĂ郍[JCYꂽ܂B + /// "翻訳対象プロセス" に類似しているローカライズされた文字列を検索します。 /// public static string TargetProcesses => ResourceManager.GetString("TargetProcesses", resourceCulture) ?? string.Empty; /// - /// "ΏۂƂ̐ݒ" ɗގĂ郍[JCYꂽ܂B + /// "対象ごとの設定" に類似しているローカライズされた文字列を検索します。 /// public static string TargetSpecificSettings => ResourceManager.GetString("TargetSpecificSettings", resourceCulture) ?? string.Empty; /// - /// "Av" ɗގĂ郍[JCYꂽ܂B + /// "アプリ名" に類似しているローカライズされた文字列を検索します。 /// public static string Title => ResourceManager.GetString("Title", resourceCulture) ?? string.Empty; /// - /// "ON/OFF؂ւ" ɗގĂ郍[JCYꂽ܂B + /// "押してON/OFFを切り替える" に類似しているローカライズされた文字列を検索します。 /// public static string Toggle => ResourceManager.GetString("Toggle", resourceCulture) ?? string.Empty; /// - /// "ݒ" ɗގĂ郍[JCYꂽ܂B + /// "言語設定" に類似しているローカライズされた文字列を検索します。 /// public static string TranslateLanguage => ResourceManager.GetString("TranslateLanguage", resourceCulture) ?? string.Empty; /// - /// "|󃂃W[" ɗގĂ郍[JCYꂽ܂B + /// "翻訳モジュール" に類似しているローカライズされた文字列を検索します。 /// public static string TranslateModule => ResourceManager.GetString("TranslateModule", resourceCulture) ?? string.Empty; /// - /// "sȃG[܂" ɗގĂ郍[JCYꂽ܂B + /// "不明なエラーが発生しました" に類似しているローカライズされた文字列を検索します。 /// public static string UnhundledErrorMessage => ResourceManager.GetString("UnhundledErrorMessage", resourceCulture) ?? string.Empty; /// - /// "IEBhEu{0}v̓vZXłȂ߁ALv`[o܂B&#13;..." ɗގĂ郍[JCYꂽ܂B + /// "アンインストール" に類似しているローカライズされた文字列を検索します。 + /// + public static string Uninstall => ResourceManager.GetString("Uninstall", resourceCulture) ?? string.Empty; + + /// + /// "{0} をアンインストールしますか?次回起動時に完全に削除されます。" に類似しているローカライズされた文字列を検索します。 + /// + public static string UninstallConfirm => ResourceManager.GetString("UninstallConfirm", resourceCulture) ?? string.Empty; + + /// + /// "選択したウィンドウ「{0}」はプロセスを特定できないため、キャプチャー出来ません。&#10;..." に類似しているローカライズされた文字列を検索します。 /// public static string UnknownWindow => ResourceManager.GetString("UnknownWindow", resourceCulture) ?? string.Empty; /// - /// "{0}̎N܂B" ɗގĂ郍[JCYꂽ܂B + /// "{0}の自動起動を解除しました。" に類似しているローカライズされた文字列を検索します。 /// public static string UnregisterAutoStart => ResourceManager.GetString("UnregisterAutoStart", resourceCulture) ?? string.Empty; /// - /// "ŐVo[WɍXV" ɗގĂ郍[JCYꂽ܂B + /// "更新" に類似しているローカライズされた文字列を検索します。 + /// + public static string Update => ResourceManager.GetString("Update", resourceCulture) ?? string.Empty; + + /// + /// "更新あり" に類似しているローカライズされた文字列を検索します。 + /// + public static string UpdateAvailable => ResourceManager.GetString("UpdateAvailable", resourceCulture) ?? string.Empty; + + /// + /// "インストール済み: {0} → 最新: {1}" に類似しているローカライズされた文字列を検索します。 + /// + public static string UpdateAvailableVersion => ResourceManager.GetString("UpdateAvailableVersion", resourceCulture) ?? string.Empty; + + /// + /// "最新バージョンに更新" に類似しているローカライズされた文字列を検索します。 /// public static string UpdateCommand => ResourceManager.GetString("UpdateCommand", resourceCulture) ?? string.Empty; /// - /// "XV" ɗގĂ郍[JCYꂽ܂B + /// "更新情報" に類似しているローカライズされた文字列を検索します。 /// public static string UpdateInfo => ResourceManager.GetString("UpdateInfo", resourceCulture) ?? string.Empty; /// - /// "o[W" ɗގĂ郍[JCYꂽ܂B + /// "バージョン" に類似しているローカライズされた文字列を検索します。 /// public static string Version => ResourceManager.GetString("Version", resourceCulture) ?? string.Empty; /// - /// "|󌋉ʕ\[h" ɗގĂ郍[JCYꂽ܂B + /// "翻訳結果表示モード" に類似しているローカライズされた文字列を検索します。 /// public static string ViewMode => ResourceManager.GetString("ViewMode", resourceCulture) ?? string.Empty; /// - /// "WindowsWF" ɗގĂ郍[JCYꂽ܂B + /// "Windows標準文字認識" に類似しているローカライズされた文字列を検索します。 /// public static string WindowsMediaOcr => ResourceManager.GetString("WindowsMediaOcr", resourceCulture) ?? string.Empty; /// - /// "r[" ɗގĂ郍[JCYꂽ܂B + /// "レビューする" に類似しているローカライズされた文字列を検索します。 /// public static string WriteReview => ResourceManager.GetString("WriteReview", resourceCulture) ?? string.Empty; } diff --git a/WindowTranslator/Properties/Resources.ar.resx b/WindowTranslator/Properties/Resources.ar.resx index 63af0458..dba7cf5a 100644 --- a/WindowTranslator/Properties/Resources.ar.resx +++ b/WindowTranslator/Properties/Resources.ar.resx @@ -447,4 +447,55 @@ + + متجر المكونات الإضافية + + + تثبيت + + + مثبت + + + تحديث + + + إلغاء التثبيت + + + هل أنت متأكد من رغبتك في إلغاء تثبيت {0}؟ سيتم حذفه بالكامل عند التشغيل التالي. + + + يتوفر تحديث + + + مثبت: {0} → الأحدث: {1} + + + مثبت: {0} + + + الإصدار المثبت + + + أحدث إصدار + + + فشل استرداد قائمة المكونات الإضافية من NuGet. يرجى التحقق من اتصالك بالشبكة. + + + اكتمل التثبيت + + + فشل التثبيت + + + يرجى إعادة تشغيل WindowTranslator لتطبيق تغييرات المكون الإضافي. + + + صفحة المشروع + + + معلومات الترخيص + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.cs.resx b/WindowTranslator/Properties/Resources.cs.resx index 83b05eb1..69ba7493 100644 --- a/WindowTranslator/Properties/Resources.cs.resx +++ b/WindowTranslator/Properties/Resources.cs.resx @@ -343,4 +343,55 @@ Monitory nejsou podporovány. + + Obchod s pluginy + + + Nainstalovat + + + Nainstalováno + + + Aktualizovat + + + Odinstalovat + + + Opravdu chcete odinstalovat {0}? Bude zcela odstraněn při příštím spuštění. + + + Dostupná aktualizace + + + Nainstalováno: {0} → Nejnovější: {1} + + + Nainstalováno: {0} + + + Nainstalovaná verze + + + Nejnovější verze + + + Nepodařilo se načíst seznam pluginů z NuGet. Zkontrolujte připojení k síti. + + + Instalace dokončena + + + Instalace se nezdařila + + + Restartujte WindowTranslator, aby se změny pluginu projevily. + + + Stránka projektu + + + Informace o licenci + diff --git a/WindowTranslator/Properties/Resources.de.resx b/WindowTranslator/Properties/Resources.de.resx index 00177fe2..0c653608 100644 --- a/WindowTranslator/Properties/Resources.de.resx +++ b/WindowTranslator/Properties/Resources.de.resx @@ -456,4 +456,55 @@ Monitore werden nicht unterstützt. + + Plugin-Store + + + Installieren + + + Installiert + + + Aktualisieren + + + Deinstallieren + + + Möchten Sie {0} wirklich deinstallieren? Es wird beim nächsten Start vollständig entfernt. + + + Update verfügbar + + + Installiert: {0} → Aktuell: {1} + + + Installiert: {0} + + + Installierte Version + + + Neueste Version + + + Fehler beim Abrufen der Plugin-Liste von NuGet. Bitte überprüfen Sie Ihre Netzwerkverbindung. + + + Installation abgeschlossen + + + Installation fehlgeschlagen + + + Bitte starten Sie WindowTranslator neu, um Plugin-Änderungen anzuwenden. + + + Projektseite + + + Lizenzinformationen + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.en.resx b/WindowTranslator/Properties/Resources.en.resx index 74c0acfa..7f9058d2 100644 --- a/WindowTranslator/Properties/Resources.en.resx +++ b/WindowTranslator/Properties/Resources.en.resx @@ -456,4 +456,55 @@ Monitors are not supported. + + Plugin Store + + + Install + + + Installed + + + Update + + + Uninstall + + + Are you sure you want to uninstall {0}? It will be fully removed on next startup. + + + Update available + + + Installed: {0} → Latest: {1} + + + Installed: {0} + + + Installed version + + + Latest version + + + Failed to retrieve plugin list from NuGet. Please check your network connection. + + + Installation complete + + + Installation failed + + + Please restart WindowTranslator to apply plugin changes. + + + Project page + + + License information + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.es.resx b/WindowTranslator/Properties/Resources.es.resx index d22a3c3a..7521e7a6 100644 --- a/WindowTranslator/Properties/Resources.es.resx +++ b/WindowTranslator/Properties/Resources.es.resx @@ -447,4 +447,55 @@ + + Tienda de plugins + + + Instalar + + + Instalado + + + Actualizar + + + Desinstalar + + + ¿Está seguro de que desea desinstalar {0}? Se eliminará completamente en el próximo inicio. + + + Actualización disponible + + + Instalado: {0} → Último: {1} + + + Instalado: {0} + + + Versión instalada + + + Última versión + + + Error al recuperar la lista de plugins de NuGet. Compruebe su conexión de red. + + + Instalación completada + + + Error de instalación + + + Reinicie WindowTranslator para aplicar los cambios del plugin. + + + Página del proyecto + + + Información de licencia + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.fa.resx b/WindowTranslator/Properties/Resources.fa.resx index ce535156..0b3838f9 100644 --- a/WindowTranslator/Properties/Resources.fa.resx +++ b/WindowTranslator/Properties/Resources.fa.resx @@ -447,4 +447,55 @@ + + فروشگاه افزونه + + + نصب + + + نصب شده + + + به‌روزرسانی + + + حذف + + + آیا مطمئن هستید که می‌خواهید {0} را حذف کنید؟ در راه‌اندازی بعدی به طور کامل حذف خواهد شد. + + + به‌روزرسانی موجود است + + + نصب شده: {0} → جدیدترین: {1} + + + نصب شده: {0} + + + نسخه نصب شده + + + جدیدترین نسخه + + + دریافت لیست افزونه از NuGet ناموفق بود. لطفاً اتصال شبکه خود را بررسی کنید. + + + نصب کامل شد + + + نصب ناموفق بود + + + لطفاً WindowTranslator را مجدداً راه‌اندازی کنید تا تغییرات افزونه اعمال شود. + + + صفحه پروژه + + + اطلاعات مجوز + diff --git a/WindowTranslator/Properties/Resources.fil.resx b/WindowTranslator/Properties/Resources.fil.resx index 657dde0d..7ac38b90 100644 --- a/WindowTranslator/Properties/Resources.fil.resx +++ b/WindowTranslator/Properties/Resources.fil.resx @@ -456,4 +456,55 @@ Ang monitor ay hindi suportado. + + Plugin Store + + + I-install + + + Naka-install + + + I-update + + + I-uninstall + + + Sigurado ka bang gusto mong i-uninstall ang {0}? Ito ay ganap na matatanggal sa susunod na pagsisimula. + + + Available ang update + + + Naka-install: {0} → Pinakabago: {1} + + + Naka-install: {0} + + + Naka-install na bersyon + + + Pinakabagong bersyon + + + Nabigo sa pagkuha ng listahan ng plugin mula sa NuGet. Pakisuri ang iyong koneksyon sa network. + + + Natapos ang pag-install + + + Nabigo ang pag-install + + + Mangyaring i-restart ang WindowTranslator upang mailapat ang mga pagbabago sa plugin. + + + Pahina ng proyekto + + + Impormasyon ng lisensya + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.fr.resx b/WindowTranslator/Properties/Resources.fr.resx index f4c7038c..258d72e0 100644 --- a/WindowTranslator/Properties/Resources.fr.resx +++ b/WindowTranslator/Properties/Resources.fr.resx @@ -447,4 +447,55 @@ + + Boutique de plugins + + + Installer + + + Installé + + + Mettre à jour + + + Désinstaller + + + Voulez-vous vraiment désinstaller {0} ? Il sera complètement supprimé au prochain démarrage. + + + Mise à jour disponible + + + Installé : {0} → Dernier : {1} + + + Installé : {0} + + + Version installée + + + Dernière version + + + Échec de la récupération de la liste des plugins depuis NuGet. Vérifiez votre connexion réseau. + + + Installation terminée + + + Échec de l'installation + + + Veuillez redémarrer WindowTranslator pour appliquer les modifications de plugin. + + + Page du projet + + + Informations de licence + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.hi.resx b/WindowTranslator/Properties/Resources.hi.resx index e93cc645..ba7b6383 100644 --- a/WindowTranslator/Properties/Resources.hi.resx +++ b/WindowTranslator/Properties/Resources.hi.resx @@ -455,4 +455,55 @@ + + Plugin Store + + + Install + + + Installed + + + Update + + + Uninstall + + + Are you sure you want to uninstall {0}? It will be fully removed on next startup. + + + Update available + + + Installed: {0} → Latest: {1} + + + Installed: {0} + + + Installed version + + + Latest version + + + Failed to retrieve plugin list from NuGet. Please check your network connection. + + + Installation complete + + + Installation failed + + + Please restart WindowTranslator to apply plugin changes. + + + Project page + + + License information + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.id.resx b/WindowTranslator/Properties/Resources.id.resx index 494a9598..ca130e23 100644 --- a/WindowTranslator/Properties/Resources.id.resx +++ b/WindowTranslator/Properties/Resources.id.resx @@ -455,4 +455,55 @@ Monitor tidak didukung. + + Toko Plugin + + + Pasang + + + Terpasang + + + Perbarui + + + Hapus + + + Apakah Anda yakin ingin menghapus {0}? Ini akan dihapus sepenuhnya saat startup berikutnya. + + + Pembaruan tersedia + + + Terpasang: {0} → Terbaru: {1} + + + Terpasang: {0} + + + Versi terpasang + + + Versi terbaru + + + Gagal mengambil daftar plugin dari NuGet. Silakan periksa koneksi jaringan Anda. + + + Instalasi selesai + + + Instalasi gagal + + + Silakan restart WindowTranslator untuk menerapkan perubahan plugin. + + + Halaman proyek + + + Informasi lisensi + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.ko.resx b/WindowTranslator/Properties/Resources.ko.resx index 186b4ee9..a285463a 100644 --- a/WindowTranslator/Properties/Resources.ko.resx +++ b/WindowTranslator/Properties/Resources.ko.resx @@ -456,4 +456,55 @@ + + 플러그인 스토어 + + + 설치 + + + 설치됨 + + + 업데이트 + + + 제거 + + + {0}을(를) 제거하시겠습니까? 다음 시작 시 완전히 제거됩니다. + + + 업데이트 있음 + + + 설치됨: {0} → 최신: {1} + + + 설치됨: {0} + + + 설치된 버전 + + + 최신 버전 + + + NuGet에서 플러그인 목록을 가져오지 못했습니다. 네트워크 연결을 확인하세요. + + + 설치 완료 + + + 설치 실패 + + + 플러그인 변경 사항을 적용하려면 WindowTranslator를 다시 시작하세요. + + + 프로젝트 페이지 + + + 라이선스 정보 + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.ms.resx b/WindowTranslator/Properties/Resources.ms.resx index 6f2dbe69..34fc87e5 100644 --- a/WindowTranslator/Properties/Resources.ms.resx +++ b/WindowTranslator/Properties/Resources.ms.resx @@ -455,4 +455,55 @@ Monitor tidak disokong. + + Kedai Plugin + + + Pasang + + + Dipasang + + + Kemaskini + + + Nyahpasang + + + Adakah anda pasti mahu menyahpasang {0}? Ia akan dibuang sepenuhnya semasa permulaan seterusnya. + + + Kemaskini tersedia + + + Dipasang: {0} → Terkini: {1} + + + Dipasang: {0} + + + Versi yang dipasang + + + Versi terkini + + + Gagal mendapatkan senarai plugin dari NuGet. Sila semak sambungan rangkaian anda. + + + Pemasangan selesai + + + Pemasangan gagal + + + Sila mulakan semula WindowTranslator untuk menerapkan perubahan plugin. + + + Halaman projek + + + Maklumat lesen + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.pl.resx b/WindowTranslator/Properties/Resources.pl.resx index 93400dac..45d1c620 100644 --- a/WindowTranslator/Properties/Resources.pl.resx +++ b/WindowTranslator/Properties/Resources.pl.resx @@ -456,4 +456,55 @@ Monitory nie są obsługiwane. + + Sklep wtyczek + + + Zainstaluj + + + Zainstalowano + + + Aktualizuj + + + Odinstaluj + + + Czy na pewno chcesz odinstalować {0}? Zostanie całkowicie usunięty przy następnym uruchomieniu. + + + Dostępna aktualizacja + + + Zainstalowano: {0} → Najnowszy: {1} + + + Zainstalowano: {0} + + + Zainstalowana wersja + + + Najnowsza wersja + + + Nie udało się pobrać listy wtyczek z NuGet. Sprawdź połączenie sieciowe. + + + Instalacja zakończona + + + Instalacja nie powiodła się + + + Uruchom ponownie WindowTranslator, aby zastosować zmiany wtyczki. + + + Strona projektu + + + Informacje o licencji + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.pt-BR.resx b/WindowTranslator/Properties/Resources.pt-BR.resx index 9ae483a0..abb41766 100644 --- a/WindowTranslator/Properties/Resources.pt-BR.resx +++ b/WindowTranslator/Properties/Resources.pt-BR.resx @@ -455,4 +455,55 @@ Monitor tidak didukung. + + Loja de Plugins + + + Instalar + + + Instalado + + + Atualizar + + + Desinstalar + + + Tem certeza que deseja desinstalar {0}? Ele será completamente removido na próxima inicialização. + + + Atualização disponível + + + Instalado: {0} → Mais recente: {1} + + + Instalado: {0} + + + Versão instalada + + + Versão mais recente + + + Falha ao recuperar lista de plugins do NuGet. Verifique sua conexão de rede. + + + Instalação concluída + + + Falha na instalação + + + Reinicie o WindowTranslator para aplicar as alterações de plugin. + + + Página do projeto + + + Informações de licença + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.resx b/WindowTranslator/Properties/Resources.resx index 5e6bfd5f..91b432e6 100644 --- a/WindowTranslator/Properties/Resources.resx +++ b/WindowTranslator/Properties/Resources.resx @@ -456,4 +456,55 @@ + + プラグインストア + + + インストール + + + インストール済み + + + 更新 + + + アンインストール + + + {0} をアンインストールしますか?次回起動時に完全に削除されます。 + + + 更新あり + + + インストール済み: {0} → 最新: {1} + + + インストール済み: {0} + + + インストール済みバージョン + + + 最新バージョン + + + NuGetからのプラグイン一覧の取得に失敗しました。ネットワーク接続を確認してください。 + + + インストール完了 + + + インストール失敗 + + + プラグインの変更を適用するには、WindowTranslatorを再起動してください。 + + + プロジェクトページ + + + ライセンス情報 + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.ru.resx b/WindowTranslator/Properties/Resources.ru.resx index dd98967c..065397e9 100644 --- a/WindowTranslator/Properties/Resources.ru.resx +++ b/WindowTranslator/Properties/Resources.ru.resx @@ -447,4 +447,55 @@ + + Магазин плагинов + + + Установить + + + Установлен + + + Обновить + + + Удалить + + + Вы уверены, что хотите удалить {0}? Он будет полностью удалён при следующем запуске. + + + Доступно обновление + + + Установлен: {0} → Последний: {1} + + + Установлен: {0} + + + Установленная версия + + + Последняя версия + + + Не удалось получить список плагинов из NuGet. Проверьте подключение к сети. + + + Установка завершена + + + Ошибка установки + + + Перезапустите WindowTranslator для применения изменений плагина. + + + Страница проекта + + + Информация о лицензии + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.th.resx b/WindowTranslator/Properties/Resources.th.resx index efb6f04e..7056a265 100644 --- a/WindowTranslator/Properties/Resources.th.resx +++ b/WindowTranslator/Properties/Resources.th.resx @@ -456,4 +456,55 @@ + + ร้านปลั๊กอิน + + + ติดตั้ง + + + ติดตั้งแล้ว + + + อัปเดต + + + ถอนการติดตั้ง + + + คุณแน่ใจหรือไม่ว่าต้องการถอนการติดตั้ง {0}? จะถูกลบออกอย่างสมบูรณ์เมื่อเริ่มต้นครั้งถัดไป + + + มีการอัปเดต + + + ติดตั้งแล้ว: {0} → ล่าสุด: {1} + + + ติดตั้งแล้ว: {0} + + + เวอร์ชันที่ติดตั้ง + + + เวอร์ชันล่าสุด + + + ไม่สามารถดึงรายการปลั๊กอินจาก NuGet ได้ โปรดตรวจสอบการเชื่อมต่อเครือข่าย + + + การติดตั้งเสร็จสมบูรณ์ + + + การติดตั้งล้มเหลว + + + กรุณาเริ่ม WindowTranslator ใหม่เพื่อนำการเปลี่ยนแปลงปลั๊กอินไปใช้ + + + หน้าโครงการ + + + ข้อมูลใบอนุญาต + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.tr.resx b/WindowTranslator/Properties/Resources.tr.resx index cd9c45fa..b2ec77de 100644 --- a/WindowTranslator/Properties/Resources.tr.resx +++ b/WindowTranslator/Properties/Resources.tr.resx @@ -456,4 +456,55 @@ Monitör desteklenmiyor. + + Eklenti Mağazası + + + Yükle + + + Yüklü + + + Güncelle + + + Kaldır + + + {0} öğesini kaldırmak istediğinizden emin misiniz? Sonraki başlatmada tamamen silinecek. + + + Güncelleme mevcut + + + Yüklü: {0} → En son: {1} + + + Yüklü: {0} + + + Yüklü sürüm + + + En son sürüm + + + NuGet'ten eklenti listesi alınamadı. Lütfen ağ bağlantınızı kontrol edin. + + + Kurulum tamamlandı + + + Kurulum başarısız + + + Eklenti değişikliklerini uygulamak için lütfen WindowTranslator'ı yeniden başlatın. + + + Proje sayfası + + + Lisans bilgileri + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.vi.resx b/WindowTranslator/Properties/Resources.vi.resx index 81d73a70..19e07b60 100644 --- a/WindowTranslator/Properties/Resources.vi.resx +++ b/WindowTranslator/Properties/Resources.vi.resx @@ -456,4 +456,55 @@ Màn hình không được hỗ trợ. + + Cửa hàng Plugin + + + Cài đặt + + + Đã cài đặt + + + Cập nhật + + + Gỡ cài đặt + + + Bạn có chắc muốn gỡ cài đặt {0}? Nó sẽ được xóa hoàn toàn khi khởi động lại. + + + Có cập nhật + + + Đã cài: {0} → Mới nhất: {1} + + + Đã cài: {0} + + + Phiên bản đã cài + + + Phiên bản mới nhất + + + Không thể lấy danh sách plugin từ NuGet. Vui lòng kiểm tra kết nối mạng. + + + Cài đặt hoàn tất + + + Cài đặt thất bại + + + Vui lòng khởi động lại WindowTranslator để áp dụng thay đổi plugin. + + + Trang dự án + + + Thông tin giấy phép + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.zh-CN.resx b/WindowTranslator/Properties/Resources.zh-CN.resx index feb1e119..0699b4d5 100644 --- a/WindowTranslator/Properties/Resources.zh-CN.resx +++ b/WindowTranslator/Properties/Resources.zh-CN.resx @@ -456,4 +456,55 @@ + + 插件商店 + + + 安装 + + + 已安装 + + + 更新 + + + 卸载 + + + 确定要卸载 {0} 吗?下次启动时将完全移除。 + + + 有更新 + + + 已安装: {0} → 最新: {1} + + + 已安装: {0} + + + 已安装版本 + + + 最新版本 + + + 无法从 NuGet 获取插件列表。请检查您的网络连接。 + + + 安装完成 + + + 安装失败 + + + 请重启 WindowTranslator 以应用插件更改。 + + + 项目页面 + + + 许可证信息 + \ No newline at end of file diff --git a/WindowTranslator/Properties/Resources.zh-TW.resx b/WindowTranslator/Properties/Resources.zh-TW.resx index 784cc9d9..8f4708b8 100644 --- a/WindowTranslator/Properties/Resources.zh-TW.resx +++ b/WindowTranslator/Properties/Resources.zh-TW.resx @@ -456,4 +456,55 @@ + + 外掛程式商店 + + + 安裝 + + + 已安裝 + + + 更新 + + + 解除安裝 + + + 確定要解除安裝 {0} 嗎?下次啟動時將完全移除。 + + + 有更新 + + + 已安裝: {0} → 最新: {1} + + + 已安裝: {0} + + + 已安裝版本 + + + 最新版本 + + + 無法從 NuGet 取得外掛程式清單。請檢查您的網路連線。 + + + 安裝完成 + + + 安裝失敗 + + + 請重新啟動 WindowTranslator 以套用外掛程式變更。 + + + 專案頁面 + + + 授權資訊 + \ No newline at end of file From 1364f7aa872061a6a0cbc33b1e3f99a9d0db1c08 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 2 May 2026 14:46:49 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=E3=83=AC=E3=83=93=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E3=83=95=E3=82=A3=E3=83=BC=E3=83=89=E3=83=90=E3=83=83=E3=82=AF?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C=E3=81=A8=E3=82=B5=E3=83=B3=E3=83=97=E3=83=AB?= =?UTF-8?q?=E3=83=97=E3=83=AD=E3=82=B8=E3=82=A7=E3=82=AF=E3=83=88=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/Freeesia/WindowTranslator/sessions/5fefad82-801f-4158-ad6f-9d7500bf50ed Co-authored-by: Freeesia <9002657+Freeesia@users.noreply.github.com> --- Samples/README.md | 135 ++++++++++++++++++ .../SampleTranslateModule.cs | 39 +++++ .../WindowTranslator.Plugin.Sample.csproj | 35 +++++ .../Modules/PluginStore/NuGetPluginService.cs | 5 +- WindowTranslator/Properties/Resources.hi.resx | 34 ++--- 5 files changed, 230 insertions(+), 18 deletions(-) create mode 100644 Samples/README.md create mode 100644 Samples/WindowTranslator.Plugin.Sample/SampleTranslateModule.cs create mode 100644 Samples/WindowTranslator.Plugin.Sample/WindowTranslator.Plugin.Sample.csproj diff --git a/Samples/README.md b/Samples/README.md new file mode 100644 index 00000000..0b0fe1b7 --- /dev/null +++ b/Samples/README.md @@ -0,0 +1,135 @@ +# WindowTranslator 外部プラグイン開発ガイド + +## 概要 + +WindowTranslator は外部プラグインによる機能拡張をサポートしています。 +NuGet パッケージとしてプラグインを公開することで、他のユーザーがアプリ内から簡単にインストールできます。 + +## クイックスタート + +### 1. プロジェクト作成 + +```bash +dotnet new classlib -n WindowTranslator.Plugin.YourPlugin +cd WindowTranslator.Plugin.YourPlugin +``` + +### 2. .csproj を設定 + +最小構成の `.csproj` 例: + +```xml + + + net10.0 + true + + + WindowTranslator.Plugin.YourPlugin + 1.0.0 + YourName + 説明文 + + windowtranslator-plugin + MIT + + + + + + + + + + + +``` + +> **重要**: `` に `windowtranslator-plugin` を含めることで、 +> WindowTranslator アプリ内のプラグインストアに表示されます。 + +### 3. プラグインを実装 + +対象のインターフェースを実装します: + +| インターフェース | 用途 | +|---|---| +| `ITranslateModule` | テキスト翻訳 | +| `IOcrModule` | 画像からテキスト認識 | +| `ICaptureModule` | ウィンドウキャプチャ | +| `IFilterModule` | 翻訳前後のテキスト加工 | +| `IColorModule` | 色変換 | +| `ICacheModule` | 翻訳結果キャッシュ | + +```csharp +using System.ComponentModel; +using WindowTranslator.Modules; + +[DisplayName("MyPlugin 翻訳")] +public class MyTranslateModule : ITranslateModule +{ + public async IAsyncEnumerable TranslateAsync( + IAsyncEnumerable texts, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await foreach (var text in texts.WithCancellation(cancellationToken)) + { + yield return await MyTranslateApiAsync(text, cancellationToken); + } + } + + public ValueTask DisposeAsync() => ValueTask.CompletedTask; +} +``` + +### 4. パッケージをビルドして NuGet に公開 + +```bash +dotnet pack -c Release -o ./nupkg +dotnet nuget push ./nupkg/WindowTranslator.Plugin.YourPlugin.1.0.0.nupkg \ + --api-key YOUR_API_KEY \ + --source https://api.nuget.org/v3/index.json +``` + +## プラグイン設定パラメータ + +プラグインに設定画面を追加するには `IPluginParam` を実装します: + +```csharp +using PropertyTools.DataAnnotations; +using WindowTranslator; + +public class MyPluginParam : IPluginParam +{ + [Category("API設定")] + [DisplayName("APIキー")] + public string ApiKey { get; set; } = string.Empty; + + [Category("翻訳設定")] + [DisplayName("翻訳元言語")] + public string SourceLanguage { get; set; } = "ja"; +} +``` + +## デフォルトモジュールの指定 + +プラグインをデフォルトとして使用させるには `[DefaultModule]` 属性を付与します: + +```csharp +[DefaultModule] +[DisplayName("My 翻訳")] +public class MyTranslateModule : ITranslateModule { ... } +``` + +## プラグインインストール先 + +インストールされたプラグインは以下のフォルダに配置されます: + +- Windows: `%USERPROFILE%\.wt\plugins\{PackageId}\` + +## 注意事項 + +- プラグインは .NET 10 以上をターゲットにしてください +- `true` を必ず設定してください +- ホスト側で既に提供されているパッケージは `ExcludeAssets="runtime"` を設定し、DLL を重複させないようにしてください +- プラグインに必要な独自の依存 DLL はすべて `lib/net10.0/` フォルダに含めてください diff --git a/Samples/WindowTranslator.Plugin.Sample/SampleTranslateModule.cs b/Samples/WindowTranslator.Plugin.Sample/SampleTranslateModule.cs new file mode 100644 index 00000000..59b81a4b --- /dev/null +++ b/Samples/WindowTranslator.Plugin.Sample/SampleTranslateModule.cs @@ -0,0 +1,39 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using Microsoft.Extensions.Logging; +using WindowTranslator.Modules; + +namespace WindowTranslator.Plugin.Sample; + +/// +/// サンプル翻訳モジュールです。 +/// テキストをそのまま返す(翻訳しない)実装例です。 +/// +[DisplayName("サンプル翻訳")] +public class SampleTranslateModule : ITranslateModule +{ + private readonly ILogger logger; + + public SampleTranslateModule(ILogger logger) + { + this.logger = logger; + } + + /// + public async IAsyncEnumerable TranslateAsync( + IAsyncEnumerable texts, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await foreach (var text in texts.WithCancellation(cancellationToken)) + { + this.logger.LogDebug("翻訳: {Text}", text); + // TODO: ここで実際の翻訳処理を実装してください + yield return $"[翻訳済み] {text}"; + } + } + + public ValueTask DisposeAsync() + { + return ValueTask.CompletedTask; + } +} diff --git a/Samples/WindowTranslator.Plugin.Sample/WindowTranslator.Plugin.Sample.csproj b/Samples/WindowTranslator.Plugin.Sample/WindowTranslator.Plugin.Sample.csproj new file mode 100644 index 00000000..7bb0e01b --- /dev/null +++ b/Samples/WindowTranslator.Plugin.Sample/WindowTranslator.Plugin.Sample.csproj @@ -0,0 +1,35 @@ + + + + + net10.0 + + true + + + WindowTranslator.Plugin.Sample + 1.0.0 + YourName + WindowTranslator サンプルプラグイン + windowtranslator-plugin + https://github.com/yourname/windowtranslator-plugin-sample + MIT + false + + + + + + + + + false + runtime + + + + + + + + diff --git a/WindowTranslator/Modules/PluginStore/NuGetPluginService.cs b/WindowTranslator/Modules/PluginStore/NuGetPluginService.cs index 3f0c81bb..5e01aa89 100644 --- a/WindowTranslator/Modules/PluginStore/NuGetPluginService.cs +++ b/WindowTranslator/Modules/PluginStore/NuGetPluginService.cs @@ -33,7 +33,10 @@ public sealed class NuGetPluginService : IDisposable public NuGetPluginService(ILogger logger) { - this.httpClient = new HttpClient(); + this.httpClient = new HttpClient + { + Timeout = TimeSpan.FromSeconds(30), + }; this.logger = logger; } diff --git a/WindowTranslator/Properties/Resources.hi.resx b/WindowTranslator/Properties/Resources.hi.resx index ba7b6383..2eb684cc 100644 --- a/WindowTranslator/Properties/Resources.hi.resx +++ b/WindowTranslator/Properties/Resources.hi.resx @@ -456,54 +456,54 @@ - Plugin Store + प्लगइन स्टोर - Install + इंस्टॉल करें - Installed + इंस्टॉल है - Update + अपडेट करें - Uninstall + अनइंस्टॉल करें - Are you sure you want to uninstall {0}? It will be fully removed on next startup. + क्या आप {0} को अनइंस्टॉल करना चाहते हैं? यह अगली बार शुरू होने पर पूरी तरह हटा दिया जाएगा। - Update available + अपडेट उपलब्ध - Installed: {0} → Latest: {1} + इंस्टॉल: {0} → नवीनतम: {1} - Installed: {0} + इंस्टॉल: {0} - Installed version + इंस्टॉल किया गया संस्करण - Latest version + नवीनतम संस्करण - Failed to retrieve plugin list from NuGet. Please check your network connection. + NuGet से प्लगइन सूची प्राप्त करने में विफल। कृपया अपना नेटवर्क कनेक्शन जांचें। - Installation complete + इंस्टॉलेशन पूर्ण - Installation failed + इंस्टॉलेशन विफल - Please restart WindowTranslator to apply plugin changes. + प्लगइन परिवर्तन लागू करने के लिए कृपया WindowTranslator को पुनः आरंभ करें। - Project page + प्रोजेक्ट पेज - License information + लाइसेंस जानकारी \ No newline at end of file From fb90a494f08c4334ce84d40128fe9d97a7dd9587 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 2 May 2026 15:13:15 +0000 Subject: [PATCH 4/4] Changes before error encountered Agent-Logs-Url: https://github.com/Freeesia/WindowTranslator/sessions/49085e31-7c44-4f11-82e0-fe40172b8447 Co-authored-by: Freeesia <9002657+Freeesia@users.noreply.github.com> --- Plugins/Directory.Build.props | 9 ++ Plugins/Directory.Build.targets | 2 +- ...nslator.Plugin.DeepLTranslatePlugin.csproj | 4 + .../WindowTranslator.Plugin.FoMPlugin.csproj | 1 + ...anslator.Plugin.GitHubCopilotPlugin.csproj | 1 + ...dowTranslator.Plugin.GoogleAIPlugin.csproj | 1 + ...lator.Plugin.GoogleAppsSctiptPlugin.csproj | 3 + .../WindowTranslator.Plugin.LLMPlugin.csproj | 1 + ...WindowTranslator.Plugin.PLaMoPlugin.csproj | 1 + ...ranslator.Plugin.TesseractOCRPlugin.csproj | 1 + .../Modules/PluginStore/NuGetPluginCatalog.cs | 53 ++++++++ .../Modules/PluginStore/NuGetPluginService.cs | 127 ++++++++++++++++++ WindowTranslator/Program.cs | 18 ++- 13 files changed, 214 insertions(+), 8 deletions(-) create mode 100644 WindowTranslator/Modules/PluginStore/NuGetPluginCatalog.cs diff --git a/Plugins/Directory.Build.props b/Plugins/Directory.Build.props index ffcb5d5e..eaff4328 100644 --- a/Plugins/Directory.Build.props +++ b/Plugins/Directory.Build.props @@ -6,6 +6,15 @@ true + + + windowtranslator-plugin + https://github.com/Freeesia/WindowTranslator + Freeesia + + false + + diff --git a/Plugins/Directory.Build.targets b/Plugins/Directory.Build.targets index 24869907..0721539f 100644 --- a/Plugins/Directory.Build.targets +++ b/Plugins/Directory.Build.targets @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/Plugins/WindowTranslator.Plugin.DeepLTranslatePlugin/WindowTranslator.Plugin.DeepLTranslatePlugin.csproj b/Plugins/WindowTranslator.Plugin.DeepLTranslatePlugin/WindowTranslator.Plugin.DeepLTranslatePlugin.csproj index 1cd83d92..8c02a33f 100644 --- a/Plugins/WindowTranslator.Plugin.DeepLTranslatePlugin/WindowTranslator.Plugin.DeepLTranslatePlugin.csproj +++ b/Plugins/WindowTranslator.Plugin.DeepLTranslatePlugin/WindowTranslator.Plugin.DeepLTranslatePlugin.csproj @@ -1,5 +1,9 @@  + + true + + diff --git a/Plugins/WindowTranslator.Plugin.FoMPlugin/WindowTranslator.Plugin.FoMPlugin.csproj b/Plugins/WindowTranslator.Plugin.FoMPlugin/WindowTranslator.Plugin.FoMPlugin.csproj index 16bac979..e0c9fe00 100644 --- a/Plugins/WindowTranslator.Plugin.FoMPlugin/WindowTranslator.Plugin.FoMPlugin.csproj +++ b/Plugins/WindowTranslator.Plugin.FoMPlugin/WindowTranslator.Plugin.FoMPlugin.csproj @@ -2,6 +2,7 @@ net10.0-windows10.0.20348.0 true + true diff --git a/Plugins/WindowTranslator.Plugin.GitHubCopilotPlugin/WindowTranslator.Plugin.GitHubCopilotPlugin.csproj b/Plugins/WindowTranslator.Plugin.GitHubCopilotPlugin/WindowTranslator.Plugin.GitHubCopilotPlugin.csproj index 90dd527f..e9ea1352 100644 --- a/Plugins/WindowTranslator.Plugin.GitHubCopilotPlugin/WindowTranslator.Plugin.GitHubCopilotPlugin.csproj +++ b/Plugins/WindowTranslator.Plugin.GitHubCopilotPlugin/WindowTranslator.Plugin.GitHubCopilotPlugin.csproj @@ -1,6 +1,7 @@  net10.0-windows10.0.20348.0 + true diff --git a/Plugins/WindowTranslator.Plugin.GoogleAIPlugin/WindowTranslator.Plugin.GoogleAIPlugin.csproj b/Plugins/WindowTranslator.Plugin.GoogleAIPlugin/WindowTranslator.Plugin.GoogleAIPlugin.csproj index ecc7110a..e15c28c5 100644 --- a/Plugins/WindowTranslator.Plugin.GoogleAIPlugin/WindowTranslator.Plugin.GoogleAIPlugin.csproj +++ b/Plugins/WindowTranslator.Plugin.GoogleAIPlugin/WindowTranslator.Plugin.GoogleAIPlugin.csproj @@ -2,6 +2,7 @@ net10.0-windows10.0.20348.0 + true diff --git a/Plugins/WindowTranslator.Plugin.GoogleAppsSctiptPlugin/WindowTranslator.Plugin.GoogleAppsSctiptPlugin.csproj b/Plugins/WindowTranslator.Plugin.GoogleAppsSctiptPlugin/WindowTranslator.Plugin.GoogleAppsSctiptPlugin.csproj index e09e0417..6177f6ba 100644 --- a/Plugins/WindowTranslator.Plugin.GoogleAppsSctiptPlugin/WindowTranslator.Plugin.GoogleAppsSctiptPlugin.csproj +++ b/Plugins/WindowTranslator.Plugin.GoogleAppsSctiptPlugin/WindowTranslator.Plugin.GoogleAppsSctiptPlugin.csproj @@ -1,4 +1,7 @@  + + true + diff --git a/Plugins/WindowTranslator.Plugin.LLMPlugin/WindowTranslator.Plugin.LLMPlugin.csproj b/Plugins/WindowTranslator.Plugin.LLMPlugin/WindowTranslator.Plugin.LLMPlugin.csproj index 33feedb9..a37c1d24 100644 --- a/Plugins/WindowTranslator.Plugin.LLMPlugin/WindowTranslator.Plugin.LLMPlugin.csproj +++ b/Plugins/WindowTranslator.Plugin.LLMPlugin/WindowTranslator.Plugin.LLMPlugin.csproj @@ -1,6 +1,7 @@  net10.0-windows10.0.20348.0 + true diff --git a/Plugins/WindowTranslator.Plugin.PLaMoPlugin/WindowTranslator.Plugin.PLaMoPlugin.csproj b/Plugins/WindowTranslator.Plugin.PLaMoPlugin/WindowTranslator.Plugin.PLaMoPlugin.csproj index c1a0b6bd..bc77e53a 100644 --- a/Plugins/WindowTranslator.Plugin.PLaMoPlugin/WindowTranslator.Plugin.PLaMoPlugin.csproj +++ b/Plugins/WindowTranslator.Plugin.PLaMoPlugin/WindowTranslator.Plugin.PLaMoPlugin.csproj @@ -2,6 +2,7 @@ net10.0 + true diff --git a/Plugins/WindowTranslator.Plugin.TesseractOCRPlugin/WindowTranslator.Plugin.TesseractOCRPlugin.csproj b/Plugins/WindowTranslator.Plugin.TesseractOCRPlugin/WindowTranslator.Plugin.TesseractOCRPlugin.csproj index bcae45af..b0e61f0e 100644 --- a/Plugins/WindowTranslator.Plugin.TesseractOCRPlugin/WindowTranslator.Plugin.TesseractOCRPlugin.csproj +++ b/Plugins/WindowTranslator.Plugin.TesseractOCRPlugin/WindowTranslator.Plugin.TesseractOCRPlugin.csproj @@ -2,6 +2,7 @@ net10.0-windows10.0.20348.0 + true diff --git a/WindowTranslator/Modules/PluginStore/NuGetPluginCatalog.cs b/WindowTranslator/Modules/PluginStore/NuGetPluginCatalog.cs new file mode 100644 index 00000000..fac75a44 --- /dev/null +++ b/WindowTranslator/Modules/PluginStore/NuGetPluginCatalog.cs @@ -0,0 +1,53 @@ +using System.IO; +using Weikio.PluginFramework.Catalogs; + +namespace WindowTranslator.Modules.PluginStore; + +/// +/// NuGet経由でインストールされたプラグインを一時フォルダからロードするカタログです。 +/// ファイルロックを回避するため、読み込み前にプラグインフォルダを一時フォルダにコピーします。 +/// +public class NuGetPluginCatalog(string sourceDir, FolderPluginCatalogOptions options) + : FolderPluginCatalog( + Path.Combine(Path.GetTempPath(), "WindowTranslator", "plugins"), + options) +{ + private static readonly string TempDir = + Path.Combine(Path.GetTempPath(), "WindowTranslator", "plugins"); + + /// + public override async Task Initialize() + { + // ロック解除のために一時フォルダを削除してからコピー + if (Directory.Exists(TempDir)) + { + Directory.Delete(TempDir, recursive: true); + } + Directory.CreateDirectory(TempDir); + + if (Directory.Exists(sourceDir)) + { + // プラグインのサブフォルダのみコピー(nuget-manifest.json等のファイルはスキップ) + foreach (var subDir in Directory.GetDirectories(sourceDir)) + { + var destSubDir = Path.Combine(TempDir, Path.GetFileName(subDir)); + CopyDirectory(subDir, destSubDir); + } + } + + await base.Initialize().ConfigureAwait(false); + } + + private static void CopyDirectory(string source, string destination) + { + Directory.CreateDirectory(destination); + foreach (var file in Directory.GetFiles(source)) + { + File.Copy(file, Path.Combine(destination, Path.GetFileName(file)), overwrite: true); + } + foreach (var subDir in Directory.GetDirectories(source)) + { + CopyDirectory(subDir, Path.Combine(destination, Path.GetFileName(subDir))); + } + } +} diff --git a/WindowTranslator/Modules/PluginStore/NuGetPluginService.cs b/WindowTranslator/Modules/PluginStore/NuGetPluginService.cs index 5e01aa89..552ab054 100644 --- a/WindowTranslator/Modules/PluginStore/NuGetPluginService.cs +++ b/WindowTranslator/Modules/PluginStore/NuGetPluginService.cs @@ -16,6 +16,40 @@ public sealed class NuGetPluginService : IDisposable private const string PluginTag = "windowtranslator-plugin"; private const string NuGetFlatContainerBase = "https://api.nuget.org/v3-flatcontainer"; + /// + /// モジュール/パラメータクラス名からNuGetパッケージIDへのマッピング。 + /// アプリバンドルから除外されたプラグインの後方互換性自動インストールに使用します。 + /// + public static readonly IReadOnlyDictionary KnownClassToPackage = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + // WindowTranslator.Plugin.FoMPlugin + ["FoMFilterModule"] = "WindowTranslator.Plugin.FoMPlugin", + ["FoMOptions"] = "WindowTranslator.Plugin.FoMPlugin", + // WindowTranslator.Plugin.PLaMoPlugin + ["PLaMoTranslator"] = "WindowTranslator.Plugin.PLaMoPlugin", + ["PLaMoOptions"] = "WindowTranslator.Plugin.PLaMoPlugin", + // WindowTranslator.Plugin.GitHubCopilotPlugin + ["GitHubCopilotTranslator"] = "WindowTranslator.Plugin.GitHubCopilotPlugin", + ["GitHubCopilotOptions"] = "WindowTranslator.Plugin.GitHubCopilotPlugin", + // WindowTranslator.Plugin.DeepLTranslatePlugin + ["DeepLTranslator"] = "WindowTranslator.Plugin.DeepLTranslatePlugin", + ["DeepLOptions"] = "WindowTranslator.Plugin.DeepLTranslatePlugin", + // WindowTranslator.Plugin.GoogleAIPlugin + ["GoogleAITranslator"] = "WindowTranslator.Plugin.GoogleAIPlugin", + ["GoogleAIOcr"] = "WindowTranslator.Plugin.GoogleAIPlugin", + ["GoogleAIOptions"] = "WindowTranslator.Plugin.GoogleAIPlugin", + // WindowTranslator.Plugin.GoogleAppsSctiptPlugin + ["GasTranslator"] = "WindowTranslator.Plugin.GoogleAppsSctiptPlugin", + ["GasOptions"] = "WindowTranslator.Plugin.GoogleAppsSctiptPlugin", + // WindowTranslator.Plugin.LLMPlugin + ["LLMTranslator"] = "WindowTranslator.Plugin.LLMPlugin", + ["LLMOcr"] = "WindowTranslator.Plugin.LLMPlugin", + ["LLMOptions"] = "WindowTranslator.Plugin.LLMPlugin", + // WindowTranslator.Plugin.TesseractOCRPlugin + ["TesseractOcr"] = "WindowTranslator.Plugin.TesseractOCRPlugin", + }; + private static readonly string UserPluginsDir = Path.Combine(PathUtility.UserDir, "plugins"); private static readonly string ManifestPath = Path.Combine(UserPluginsDir, "nuget-manifest.json"); @@ -40,6 +74,95 @@ public NuGetPluginService(ILogger logger) this.logger = logger; } + /// + /// 指定したパッケージの最新バージョンをインストールします。 + /// + public async Task InstallLatestPackageAsync(string packageId, IProgress? progress = null, CancellationToken cancellationToken = default) + { + var versionsUrl = $"{NuGetFlatContainerBase}/{packageId.ToLowerInvariant()}/index.json"; + var response = await this.httpClient.GetAsync(versionsUrl, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + var content = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + var versions = await JsonSerializer.DeserializeAsync(content, JsonOptions, cancellationToken).ConfigureAwait(false); + var latestVersion = versions?.Versions?.LastOrDefault() + ?? throw new InvalidOperationException($"パッケージ {packageId} のバージョン一覧を取得できませんでした。"); + await InstallPackageAsync(packageId, latestVersion, progress, cancellationToken).ConfigureAwait(false); + } + + /// + /// 設定ファイルで参照されているがインストールされていないプラグインを自動インストールします。 + /// アプリバンドルから除外されたプラグインの後方互換性維持のために使用します。 + /// + public async Task AutoInstallFromSettingsAsync(string settingsPath, CancellationToken cancellationToken = default) + { + if (!File.Exists(settingsPath)) + { + return; + } + + try + { + using var doc = JsonDocument.Parse(await File.ReadAllTextAsync(settingsPath, cancellationToken).ConfigureAwait(false)); + var neededPackages = new HashSet(StringComparer.OrdinalIgnoreCase); + + if (doc.RootElement.TryGetProperty("Targets", out var targets)) + { + foreach (var target in targets.EnumerateObject()) + { + // SelectedPlugins の値(モジュールクラス名)をチェック + if (target.Value.TryGetProperty("SelectedPlugins", out var selectedPlugins)) + { + foreach (var plugin in selectedPlugins.EnumerateObject()) + { + var className = plugin.Value.GetString(); + if (className is not null && KnownClassToPackage.TryGetValue(className, out var packageId)) + { + neededPackages.Add(packageId); + } + } + } + + // PluginParams のキー(パラメータクラス名)をチェック + if (target.Value.TryGetProperty("PluginParams", out var pluginParams)) + { + foreach (var param in pluginParams.EnumerateObject()) + { + if (KnownClassToPackage.TryGetValue(param.Name, out var packageId)) + { + neededPackages.Add(packageId); + } + } + } + } + } + + if (neededPackages.Count == 0) + { + return; + } + + var installed = await GetInstalledPackagesAsync(cancellationToken).ConfigureAwait(false); + var installedIds = installed.Select(p => p.Id).ToHashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var packageId in neededPackages.Where(id => !installedIds.Contains(id))) + { + this.logger.LogInformation("設定で参照されているプラグインを自動インストール: {PackageId}", packageId); + try + { + await InstallLatestPackageAsync(packageId, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + this.logger.LogWarning(ex, "プラグイン {PackageId} の自動インストールに失敗しました。", packageId); + } + } + } + catch (Exception ex) + { + this.logger.LogWarning(ex, "設定からのプラグイン自動インストール処理中にエラーが発生しました。"); + } + } + /// /// NuGetでWindowTranslatorプラグインを検索します。 /// @@ -394,3 +517,7 @@ internal record NuGetSearchData( [property: JsonPropertyName("projectUrl")] string? ProjectUrl, [property: JsonPropertyName("licenseUrl")] string? LicenseUrl ); + +internal record NuGetVersionListResponse( + [property: JsonPropertyName("versions")] string[]? Versions +); diff --git a/WindowTranslator/Program.cs b/WindowTranslator/Program.cs index 7296b8be..6c2bab4a 100644 --- a/WindowTranslator/Program.cs +++ b/WindowTranslator/Program.cs @@ -59,6 +59,14 @@ var exeDir = Path.GetDirectoryName(Environment.GetCommandLineArgs()[0])!; Directory.SetCurrentDirectory(exeDir); +// ペンディング削除処理と設定参照プラグインの自動インストール(カタログ初期化より前に実行する必要がある) +{ + using var earlyLoggerFactory = LoggerFactory.Create(b => b.SetMinimumLevel(LogLevel.Warning)); + using var earlyNuGetService = new NuGetPluginService(earlyLoggerFactory.CreateLogger()); + earlyNuGetService.ProcessPendingDeletions(); + await earlyNuGetService.AutoInstallFromSettingsAsync(PathUtility.UserSettings); +} + var builder = KamishibaiApplication.CreateBuilder(); builder.Host.ConfigureLogging((c, l) => @@ -122,10 +130,9 @@ } var userPluginsDir = Path.Combine(PathUtility.UserDir, "plugins"); -if (Directory.Exists(userPluginsDir)) -{ - pluginFolderCatalog.AddCatalog(new FolderPluginCatalog(userPluginsDir, options: new() { PluginNameOptions = { PluginNameGenerator = GetPluginName } })); -} +pluginFolderCatalog.AddCatalog(new NuGetPluginCatalog( + userPluginsDir, + new() { PluginNameOptions = { PluginNameGenerator = GetPluginName } })); builder.Services.AddPluginCatalog(pluginFolderCatalog); builder.Configuration @@ -188,9 +195,6 @@ e.Window.Activate(); }; -// 起動時にペンディング削除を処理する -app.Services.GetRequiredService().ProcessPendingDeletions(); - if (SentrySdk.IsEnabled) { app.Logger.LogInformation("Sentry is enabled.");