Initial commit

This commit is contained in:
gamer147
2025-11-17 22:58:50 -05:00
commit 3bb8f7f158
63 changed files with 3733 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["FictionArchive.Service.TranslationService/FictionArchive.Service.TranslationService.csproj", "FictionArchive.Service.TranslationService/"]
RUN dotnet restore "FictionArchive.Service.TranslationService/FictionArchive.Service.TranslationService.csproj"
COPY . .
WORKDIR "/src/FictionArchive.Service.TranslationService"
RUN dotnet build "./FictionArchive.Service.TranslationService.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./FictionArchive.Service.TranslationService.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "FictionArchive.Service.TranslationService.dll"]

View File

@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="HotChocolate.AspNetCore" Version="15.1.11" />
<PackageReference Include="HotChocolate.Data.EntityFramework" Version="15.1.11" />
<PackageReference Include="HotChocolate.Types.Scalars" Version="15.1.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.11">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<PackageReference Include="DeepL.net" Version="1.17.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2"/>
</ItemGroup>
<ItemGroup>
<Content Include="..\.dockerignore">
<Link>.dockerignore</Link>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FictionArchive.Common\FictionArchive.Common.csproj" />
<ProjectReference Include="..\FictionArchive.Service.Shared\FictionArchive.Service.Shared.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,14 @@
using FictionArchive.Common.Enums;
using FictionArchive.Service.TranslationService.Services.TranslationEngines;
namespace FictionArchive.Service.TranslationService.GraphQL;
public class Mutation
{
public async Task<string> TranslateText(string text, Language from, Language to, string translationEngineKey, IEnumerable<ITranslationEngineAdapter> translationEngines)
{
var engine = translationEngines.FirstOrDefault(engine => engine.Descriptor.Key == translationEngineKey);
var translation = await engine.GetTranslation(text, from, to);
return translation;
}
}

View File

@@ -0,0 +1,14 @@
using FictionArchive.Service.TranslationService.Models;
using FictionArchive.Service.TranslationService.Services.TranslationEngines;
namespace FictionArchive.Service.TranslationService.GraphQL;
public class Query
{
[UseFiltering]
[UseSorting]
public IEnumerable<TranslationEngineDescriptor> GetTranslationEngines(IEnumerable<ITranslationEngineAdapter> engines)
{
return engines.Select(engine => engine.Descriptor);
}
}

View File

@@ -0,0 +1,7 @@
namespace FictionArchive.Service.TranslationService.Models;
public class TranslationEngineDescriptor
{
public string DisplayName { get; set; }
public string Key { get; set; }
}

View File

@@ -0,0 +1,50 @@
using DeepL;
using FictionArchive.Service.Shared.Services.GraphQL;
using FictionArchive.Service.TranslationService.GraphQL;
using FictionArchive.Service.TranslationService.Services.TranslationEngines;
using FictionArchive.Service.TranslationService.Services.TranslationEngines.DeepLTranslate;
namespace FictionArchive.Service.TranslationService;
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();
#region GraphQL
builder.Services.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>()
.AddType<UnsignedIntType>()
.AddMutationConventions(applyToAllMutations: true)
.AddFiltering(opt => opt.AddDefaults().BindRuntimeType<uint, UnsignedIntOperationFilterInputType>())
.AddSorting()
.AddProjections();
#endregion
#region Translation Adapter
builder.Services.AddTransient<DeepLClient>(provider =>
{
return new DeepLClient(builder.Configuration["DeepL:ApiKey"]);
});
builder.Services.AddTransient<ITranslationEngineAdapter, DeepLTranslationAdapater>();
#endregion
var app = builder.Build();
app.UseHttpsRedirection();
app.MapHealthChecks("/healthz");
app.MapGraphQL();
app.Run();
}
}

View File

@@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:36751",
"sslPort": 44335
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5134",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "graphql",
"applicationUrl": "https://localhost:7275;http://localhost:5134",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,51 @@
using DeepL;
using DeepL.Model;
using FictionArchive.Service.TranslationService.Models;
using Language = FictionArchive.Common.Enums.Language;
namespace FictionArchive.Service.TranslationService.Services.TranslationEngines.DeepLTranslate;
public class DeepLTranslationAdapater : ITranslationEngineAdapter
{
private readonly DeepLClient _deepLClient;
private readonly ILogger<DeepLTranslationAdapater> _logger;
private const string DisplayName = "DeepL";
private const string Key = "deepl";
public DeepLTranslationAdapater(DeepLClient deepLClient, ILogger<DeepLTranslationAdapater> logger)
{
_deepLClient = deepLClient;
_logger = logger;
}
public TranslationEngineDescriptor Descriptor
{
get
{
return new TranslationEngineDescriptor()
{
DisplayName = DisplayName,
Key = Key,
};
}
}
public async Task<string?> GetTranslation(string body, Language from, Language to)
{
TextResult translationResult = await _deepLClient.TranslateTextAsync(body, GetLanguageCode(from), GetLanguageCode(to));
_logger.LogInformation("Translated text. Usage statistics: CHARACTERS BILLED {TranslationResultBilledCharacters}", translationResult.BilledCharacters);
return translationResult.Text;
}
private string GetLanguageCode(Language language)
{
return language switch
{
Language.En => LanguageCode.EnglishAmerican,
Language.Kr => LanguageCode.Korean,
Language.Ch => LanguageCode.Chinese,
Language.Ja => LanguageCode.Japanese
};
}
}

View File

@@ -0,0 +1,10 @@
using FictionArchive.Common.Enums;
using FictionArchive.Service.TranslationService.Models;
namespace FictionArchive.Service.TranslationService.Services.TranslationEngines;
public interface ITranslationEngineAdapter
{
public TranslationEngineDescriptor Descriptor { get; }
public Task<string?> GetTranslation(string body, Language from, Language to);
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"DeepL": {
"ApiKey": "REPLACE_ME"
},
"AllowedHosts": "*"
}