Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Plugins/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>

<!-- NuGet パッケージとして公開する際の共通設定 -->
<PropertyGroup>
<PackageTags Condition="'$(PackageTags)' == ''">windowtranslator-plugin</PackageTags>
<PackageProjectUrl Condition="'$(PackageProjectUrl)' == ''">https://github.com/Freeesia/WindowTranslator</PackageProjectUrl>
<Authors Condition="'$(Authors)' == ''">Freeesia</Authors>
<!-- ExcludeFromAppBundle=true のプラグインはビルド後にアプリのpluginsフォルダにコピーされません -->
<ExcludeFromAppBundle Condition="'$(ExcludeFromAppBundle)' == ''">false</ExcludeFromAppBundle>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Extensions.Options" ExcludeAssets="runtime" />
Expand Down
2 changes: 1 addition & 1 deletion Plugins/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<!-- 上位階層の Directory.Build.targets をインクルード -->
<Import Project="$(MSBuildProjectDirectory)\..\..\Directory.Build.targets" Condition="Exists('$(MSBuildProjectDirectory)\..\..\Directory.Build.targets')" />

<Target Condition="'$(IsTestProject)' != 'true'" Name="PostBuild" AfterTargets="PostBuildEvent">
<Target Condition="'$(IsTestProject)' != 'true' AND '$(ExcludeFromAppBundle)' != 'true'" Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="call $(ProjectDir)..\copy_plugins.bat $(OutDir) $(ProjectName) $(Configuration)" />
</Target>
</Project>
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<ExcludeFromAppBundle>true</ExcludeFromAppBundle>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CsvHelper" />
<PackageReference Include="DeepL.net" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>net10.0-windows10.0.20348.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ExcludeFromAppBundle>true</ExcludeFromAppBundle>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0-windows10.0.20348.0</TargetFramework>
<ExcludeFromAppBundle>true</ExcludeFromAppBundle>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CsvHelper" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<TargetFramework>net10.0-windows10.0.20348.0</TargetFramework>
<ExcludeFromAppBundle>true</ExcludeFromAppBundle>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CsvHelper" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ExcludeFromAppBundle>true</ExcludeFromAppBundle>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Apis.Auth" />
<PackageReference Include="ValueTaskSupplement" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0-windows10.0.20348.0</TargetFramework>
<ExcludeFromAppBundle>true</ExcludeFromAppBundle>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CsvHelper" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ExcludeFromAppBundle>true</ExcludeFromAppBundle>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LLamaSharp" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<TargetFramework>net10.0-windows10.0.20348.0</TargetFramework>
<ExcludeFromAppBundle>true</ExcludeFromAppBundle>
</PropertyGroup>

<ItemGroup>
Expand Down
135 changes: 135 additions & 0 deletions Samples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# WindowTranslator 外部プラグイン開発ガイド

## 概要

WindowTranslator は外部プラグインによる機能拡張をサポートしています。
NuGet パッケージとしてプラグインを公開することで、他のユーザーがアプリ内から簡単にインストールできます。

## クイックスタート

### 1. プロジェクト作成

```bash
dotnet new classlib -n WindowTranslator.Plugin.YourPlugin
cd WindowTranslator.Plugin.YourPlugin
```

### 2. .csproj を設定

最小構成の `.csproj` 例:

```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>

<!-- NuGet パッケージ情報 -->
<PackageId>WindowTranslator.Plugin.YourPlugin</PackageId>
<Version>1.0.0</Version>
<Authors>YourName</Authors>
<Description>説明文</Description>
<!-- この タグ が必須です(アプリ内一覧への表示条件) -->
<PackageTags>windowtranslator-plugin</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>

<ItemGroup>
<!-- WindowTranslator.Abstractions を NuGet から参照 -->
<PackageReference Include="WindowTranslator.Abstractions" Version="x.y.z" ExcludeAssets="runtime" />

<!-- ホスト側で提供されるパッケージは ExcludeAssets="runtime" を設定 -->
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Extensions.Options" ExcludeAssets="runtime" />
</ItemGroup>
</Project>
```

> **重要**: `<PackageTags>` に `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<string> TranslateAsync(
IAsyncEnumerable<string> 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 以上をターゲットにしてください
- `<EnableDynamicLoading>true</EnableDynamicLoading>` を必ず設定してください
- ホスト側で既に提供されているパッケージは `ExcludeAssets="runtime"` を設定し、DLL を重複させないようにしてください
- プラグインに必要な独自の依存 DLL はすべて `lib/net10.0/` フォルダに含めてください
39 changes: 39 additions & 0 deletions Samples/WindowTranslator.Plugin.Sample/SampleTranslateModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Logging;
using WindowTranslator.Modules;

namespace WindowTranslator.Plugin.Sample;

/// <summary>
/// サンプル翻訳モジュールです。
/// テキストをそのまま返す(翻訳しない)実装例です。
/// </summary>
[DisplayName("サンプル翻訳")]
public class SampleTranslateModule : ITranslateModule
{
private readonly ILogger<SampleTranslateModule> logger;

public SampleTranslateModule(ILogger<SampleTranslateModule> logger)
{
this.logger = logger;
}

/// <inheritdoc/>
public async IAsyncEnumerable<string> TranslateAsync(
IAsyncEnumerable<string> 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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<!-- .NET 10 以降をターゲットにしてください -->
<TargetFramework>net10.0</TargetFramework>
<!-- プラグインとして動的ロードされるため必須です -->
<EnableDynamicLoading>true</EnableDynamicLoading>

<!-- NuGet パッケージ情報 -->
<PackageId>WindowTranslator.Plugin.Sample</PackageId>
<Version>1.0.0</Version>
<Authors>YourName</Authors>
<Description>WindowTranslator サンプルプラグイン</Description>
<PackageTags>windowtranslator-plugin</PackageTags>
<PackageProjectUrl>https://github.com/yourname/windowtranslator-plugin-sample</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
</PropertyGroup>

<ItemGroup>
<!-- WindowTranslator.Abstractions を NuGet 参照してください -->
<!-- <PackageReference Include="WindowTranslator.Abstractions" Version="x.y.z" ExcludeAssets="runtime" /> -->

<!-- ローカル開発時はプロジェクト参照も可 -->
<ProjectReference Include="..\..\WindowTranslator.Abstractions\WindowTranslator.Abstractions.csproj">
<Private>false</Private>
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>

<!-- ホスト側で提供されるパッケージは ExcludeAssets="runtime" を設定して重複を避けてください -->
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.2" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" ExcludeAssets="runtime" />
</ItemGroup>

</Project>
53 changes: 53 additions & 0 deletions WindowTranslator/Modules/PluginStore/NuGetPluginCatalog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.IO;
using Weikio.PluginFramework.Catalogs;

namespace WindowTranslator.Modules.PluginStore;

/// <summary>
/// NuGet経由でインストールされたプラグインを一時フォルダからロードするカタログです。
/// ファイルロックを回避するため、読み込み前にプラグインフォルダを一時フォルダにコピーします。
/// </summary>
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");

/// <inheritdoc/>
public override async Task Initialize()

Check failure on line 19 in WindowTranslator/Modules/PluginStore/NuGetPluginCatalog.cs

View workflow job for this annotation

GitHub Actions / build (false)

'NuGetPluginCatalog.Initialize()': cannot override inherited member 'FolderPluginCatalog.Initialize()' because it is not marked virtual, abstract, or override
{
// ロック解除のために一時フォルダを削除してからコピー
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)));
}
}
}
Loading
Loading