Feature: Implement translation service #1

Merged
Egamorf merged 2 commits from feature/implement-translation-service into main 2025-02-17 22:12:56 +01:00
19 changed files with 139 additions and 102 deletions

23
.gitignore vendored
View File

@@ -414,3 +414,26 @@ FodyWeavers.xsd
# JetBrains Rider # JetBrains Rider
*.sln.iml *.sln.iml
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode

View File

@@ -4,11 +4,17 @@
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>5637e3c4-2341-4bdb-85ec-c75faeee9847</UserSecretsId>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.0" PrivateAssets="all" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\GameIdeas.Resources\GameIdeas.Resources.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,4 +1,5 @@
@inherits LayoutComponentBase @inherits LayoutComponentBase
<div class="page"> <div class="page">
<div class="sidebar"> <div class="sidebar">
<NavMenu /> <NavMenu />

View File

@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Components;
using System.Net.Http.Json;
using GameIdeas.Resources;
namespace GameIdeas.BlazorApp.Layout;
public partial class MainLayout(
IHttpClientFactory HttpClientFactory,
TranslationService TranslationService,
Translations Translations) : LayoutComponentBase
{
protected override async Task OnInitializedAsync()
{
var client = HttpClientFactory.CreateClient("GameIdeas.WebAPI");
var response = await client.GetAsync("api/Translations");
var dictionary = await response.Content.ReadFromJsonAsync<Dictionary<string, string>>();
if (dictionary != null)
{
TranslationService.Initialize(dictionary);
ResourcesKey.Initialize(Translations);
}
}
}

View File

@@ -1,4 +1,5 @@
using GameIdeas.WebApp; using GameIdeas.BlazorApp;
using GameIdeas.Resources;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
@@ -6,6 +7,20 @@ var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app"); builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after"); builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); UriBuilder uriBuilder = new(builder.HostEnvironment.BaseAddress)
{
Port = 8000
};
await builder.Build().RunAsync(); builder.Services.AddHttpClient(
"GameIdeas.WebAPI",
client =>
{
client.BaseAddress = uriBuilder.Uri;
client.Timeout = TimeSpan.FromMinutes(3);
});
builder.Services.AddSingleton<TranslationService>();
builder.Services.AddSingleton<Translations>();
await builder.Build().RunAsync();

View File

@@ -6,5 +6,5 @@
@using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop @using Microsoft.JSInterop
@using GameIdeas.WebApp @using GameIdeas.BlazorApp
@using GameIdeas.WebApp.Layout @using GameIdeas.BlazorApp.Layout

View File

@@ -1,27 +0,0 @@
[
{
"date": "2022-01-06",
"temperatureC": 1,
"summary": "Freezing"
},
{
"date": "2022-01-07",
"temperatureC": 14,
"summary": "Bracing"
},
{
"date": "2022-01-08",
"temperatureC": -13,
"summary": "Freezing"
},
{
"date": "2022-01-09",
"temperatureC": -16,
"summary": "Balmy"
},
{
"date": "2022-01-10",
"temperatureC": -2,
"summary": "Chilly"
}
]

View File

@@ -1 +1,19 @@
namespace GameIdeas.Resources;
public class Translations (TranslationService translationService)
{
public string GamesIdeas => translationService.Translate(nameof(GamesIdeas));
}
public static class ResourcesKey
{
private static Translations? _instance;
public static void Initialize(Translations translations)
{
_instance = translations;
}
public static string GamesIdeas => _instance?.GamesIdeas ?? throw new InvalidOperationException("ResourcesKey.GamesIdeas is not initialized.");
}

View File

@@ -7,7 +7,7 @@
<# <#
var path = Path.GetDirectoryName(Host.TemplateFile); var path = Path.GetDirectoryName(Host.TemplateFile);
var localPath = Host.ResolvePath(Path.Combine(path,"../../Server/GameIdeas.API/Files/GameIdeas.fr.json")); var localPath = Host.ResolvePath(Path.Combine(path,"../../GameIdeas/Server/GameIdeas.WebAPI/Files/GameIdeas.fr.json"));
var json = File.ReadAllText(localPath); var json = File.ReadAllText(localPath);
@@ -121,8 +121,8 @@
// Generate the class // Generate the class
#> #>
using System; namespace GameIdeas.Resources;
namespace ArgosV2.Resources;
public class Translations (TranslationService translationService) public class Translations (TranslationService translationService)
{ {
<# <#

View File

@@ -1,7 +1,7 @@
using System.Globalization; using System.Globalization;
using System.Text.Json; using System.Text.Json;
namespace ArgosV2.Resources; namespace GameIdeas.Resources;
public class TranslationService public class TranslationService
{ {

View File

@@ -1,16 +1,13 @@
using ArgosV2.AppInsight.Logging;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Abstractions;
namespace ArgosV2.Server.WebAPI.Controllers; namespace GameIdeas.WebAPI.Controllers;
[Route("api/[controller]")]
[ApiController] [ApiController]
public class TranslationsController (ITelemetryService telemetryClient) : ControllerBase [Route("api/[controller]")]
public class TranslationsController (ILogger<TranslationsController> Logger) : ControllerBase
{ {
[Authorize] [HttpGet]
[HttpGet]
public async Task<IActionResult> GetTranslations() public async Task<IActionResult> GetTranslations()
{ {
var dictionary = new Dictionary<string, string>(); var dictionary = new Dictionary<string, string>();
@@ -31,7 +28,7 @@ public class TranslationsController (ITelemetryService telemetryClient) : Contro
} }
catch(Exception ex) catch(Exception ex)
{ {
telemetryClient.TrackException<TranslationsController>(ex); Logger.LogError(ex, "Internal translations error");
} }
return Ok(dictionary); return Ok(dictionary);

View File

@@ -1,33 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace GameIdeas.API.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}

View File

@@ -1,3 +1,3 @@
{ {
"GamesIdeas": "Game Ideas"
} }

View File

@@ -1,6 +1,6 @@
@GameIdeas.API_HostAddress = http://localhost:5190 @GameIdeas.API_HostAddress = http://localhost:8000
GET {{GameIdeas.API_HostAddress}}/weatherforecast/ GET {{GameIdeas.API_HostAddress}}/translations
Accept: application/json Accept: application/json
### ###

View File

@@ -7,15 +7,15 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Remove="Files\GameIdeas.fr.json" /> <EmbeddedResource Include="Files\GameIdeas.fr.json" />
</ItemGroup>
<ItemGroup>
<Compile Include="Files\GameIdeas.fr.json" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\GameIdeas.Resources\GameIdeas.Resources.csproj" />
</ItemGroup>
</Project> </Project>

View File

@@ -1,3 +1,6 @@
using GameIdeas.Resources;
using System.Resources;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Add services to the container. // Add services to the container.
@@ -6,6 +9,14 @@ builder.Services.AddControllers();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi(); builder.Services.AddOpenApi();
builder.Services.AddCors(option => option.AddDefaultPolicy(policy =>
policy.WithOrigins("http://localhost:5172")
.AllowAnyHeader()
.WithMethods("GET", "POST")));
builder.Services.AddSingleton<TranslationService>();
builder.Services.AddSingleton<Translations>();
var app = builder.Build(); var app = builder.Build();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
@@ -14,6 +25,21 @@ if (app.Environment.IsDevelopment())
app.MapOpenApi(); app.MapOpenApi();
} }
var filesDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Files");
var translationFiles = Directory.GetFiles(filesDirectory, "*.json");
var dictionary = new Dictionary<string, string>();
foreach (var file in translationFiles)
{
var name = file.Split('.');
var culture = name[^2];
var content = await File.ReadAllTextAsync(file);
dictionary.Add(culture, content);
}
app.Services.GetRequiredService<TranslationService>().Initialize(dictionary);
ResourcesKey.Initialize(app.Services.GetRequiredService<Translations>());
app.UseCors();
app.UseAuthorization(); app.UseAuthorization();
app.MapControllers(); app.MapControllers();

View File

@@ -5,7 +5,7 @@
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": false, "launchBrowser": false,
"applicationUrl": "http://localhost:5190", "applicationUrl": "http://localhost:8000",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }

View File

@@ -1,13 +0,0 @@
namespace GameIdeas.API
{
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
}

0
workflows/build.yaml Normal file
View File