using System.Net.Http.Headers;
using System.Text.Json;
using TaskTrackingSystem.Shared.Localization;
namespace TaskTrackingSystem.WebApi.Features.Translation;
///
/// Optional server-side provider adapter. It is inert until Translation:Endpoint is configured.
///
public sealed class ConfiguredHttpContentTranslationService : IContentTranslationService
{
private readonly IHttpClientFactory httpClientFactory;
private readonly IConfiguration configuration;
private readonly ILogger logger;
public ConfiguredHttpContentTranslationService(
IHttpClientFactory httpClientFactory,
IConfiguration configuration,
ILogger logger)
{
this.httpClientFactory = httpClientFactory;
this.configuration = configuration;
this.logger = logger;
}
public Task TranslateAsync(string sourceText, string sourceLanguage, string targetLanguage, CancellationToken cancellationToken = default) =>
SendAsync(sourceText, sourceLanguage, targetLanguage, false, cancellationToken);
public Task TransliterateNameAsync(string sourceName, string sourceLanguage, string targetLanguage, CancellationToken cancellationToken = default) =>
SendAsync(sourceName, sourceLanguage, targetLanguage, true, cancellationToken);
private async Task SendAsync(string sourceText, string sourceLanguage, string targetLanguage, bool isName, CancellationToken cancellationToken)
{
var endpoint = configuration["Translation:Endpoint"] ?? Environment.GetEnvironmentVariable("TRANSLATION_API_URL");
if (string.IsNullOrWhiteSpace(endpoint)) return TranslationResult.NotConfigured();
try
{
using var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
var key = configuration["Translation:ApiKey"] ?? Environment.GetEnvironmentVariable("TRANSLATION_API_KEY");
if (!string.IsNullOrWhiteSpace(key)) request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = JsonContent.Create(new
{
text = sourceText,
sourceLanguage,
targetLanguage,
mode = isName ? "transliteration" : "translation",
instruction = isName
? "Transliterate pronunciation/spelling only. Do not translate semantic meaning. Return the name only."
: "Translate to natural formal Burmese or English. Preserve names unless requested, codes, IDs, dates, numbers, placeholders, URLs, and appropriate technical terms. Return translated text only."
});
var client = httpClientFactory.CreateClient();
using var response = await client.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
logger.LogWarning("Translation provider returned HTTP {StatusCode}.", (int)response.StatusCode);
return new(false, null, $"Translation provider returned {(int)response.StatusCode}.", ProviderName(), false);
}
using var document = JsonDocument.Parse(await response.Content.ReadAsStringAsync(cancellationToken));
var root = document.RootElement;
var text = TryGetString(root, "translatedText") ?? TryGetString(root, "translation") ?? TryGetString(root, "text");
return string.IsNullOrWhiteSpace(text)
? new(false, null, "Translation provider returned no translated text.", ProviderName(), false)
: TranslationResult.Generated(text.Trim(), ProviderName());
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch (Exception ex)
{
logger.LogWarning("Translation provider request failed: {Message}", ex.Message);
return new(false, null, "Translation provider request failed.", ProviderName(), false);
}
}
private string ProviderName() => configuration["Translation:Provider"] ?? Environment.GetEnvironmentVariable("TRANSLATION_PROVIDER") ?? "configured-http";
private static string? TryGetString(JsonElement element, string name) =>
element.TryGetProperty(name, out var value) && value.ValueKind == JsonValueKind.String ? value.GetString() : null;
}