This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Så för ett tag nu har jag använt EasyNMT för att översätta min .md
filer till olika språk här....................................... Jag har velat "täcka" detta så att ni alla kan ha en pjäs med det också.
Se andra delen av denna serie här.
Örnen bland er har märkt att jag har lagt till en liten droppe ner till markdown redaktören.
Detta är en lista över språk som jag översätter till (EasyNMT är lite av en resurs svin så jag har begränsat antalet o språk jag kan översätta till).
När du väljer ett språk från rullgardinen, och trycker på en knapp skickar du en "översätt" uppgift till detta API:
[ApiController]
[Route("api/[controller]")]
public class TranslateController(
BackgroundTranslateService backgroundTranslateService,
TranslateCacheService translateCacheService) : ControllerBase
{
[HttpPost("start-translation")]
public async Task<IActionResult> StartTranslation([FromBody] MarkdownTranslationModel model)
{
// Create a unique identifier for this translation task
var taskId = Guid.NewGuid().ToString("N");
var userId = Request.GetUserId(Response);
// Trigger translation and store the associated task
var translationTask = await backgroundTranslateService.Translate(model);
var translateTask = new TranslateTask(taskId, model.Language, translationTask);
translateCacheService.AddTask(userId, translateTask);
// Return the task ID to the client
return Ok(new { TaskId = taskId });
}
}
Det här gör några saker:
Cookie-tjänsten är en enkel förlängning på HttpRequest-objektet. Den kontrollerar om en cookie finns, om den inte gör det skapar den en ny. Detta används för att identifiera dig och dina översättningsuppgifter.
public static class UserIdHtlper
{
public static string GetUserId(this HttpRequest request, HttpResponse response)
{
var userId = request.Cookies["UserIdentifier"];
if (userId == null)
{
userId = Guid.NewGuid().ToString();
var cookieOptions = new CookieOptions
{
Expires = DateTimeOffset.UtcNow.AddMinutes(60),
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Strict
};
response.Cookies.Append("UserIdentifier", userId, cookieOptions);
}
return userId;
}
}
cache tjänsten använder en enkel in-minne cache för att hålla alla översättningsuppgifter för en enda användare (du!). Du får se att jag har satt skatten att löpa ut efter en timme. Det är för att jag inte vill hålla fast vid de här uppgifterna för länge.
public class TranslateCacheService(IMemoryCache memoryCache)
{
public List<TranslateTask> GetTasks(string userId)
{
if (memoryCache.TryGetValue(userId, out List<TranslateTask>? task)) return task;
return new List<TranslateTask>();
}
public void AddTask(string userId, TranslateTask task)
{
if (memoryCache.TryGetValue(userId, out List<TranslateTask>? tasks))
{
tasks ??= new List<TranslateTask>();
tasks.Add(task);
memoryCache.Set(userId, tasks, new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
});
}
else
{
memoryCache.Set(userId, new List<TranslateTask> { task }, new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
});
}
}
}
public record TranslateTask(string TaskId, string language, Task<TaskCompletion>? Task);
Jag täcker det här i nästa del, det är lite av ett odjur.
Emellertid gäller följande: Translate
metod använder TaskCompletionSource för att låta oss spåra status för översättningsuppgiften.
public async Task<Task<TaskCompletion>> Translate(MarkdownTranslationModel message)
{
// Create a TaskCompletionSource that will eventually hold the result of the translation
var translateMessage = new PageTranslationModel
{
Language = message.Language,
OriginalFileName = "",
OriginalMarkdown = message.OriginalMarkdown,
Persist = false
};
return await Translate(translateMessage);
}
private async Task<Task<TaskCompletion>> Translate(PageTranslationModel message)
{
// Create a TaskCompletionSource that will eventually hold the result of the translation
var tcs = new TaskCompletionSource<TaskCompletion>();
// Send the translation request along with the TaskCompletionSource to be processed
await _translations.SendAsync((message, tcs));
return tcs.Task;
}
Som du kan se allt detta verkligen gör är att skicka en översättning till en BufferBlock
(Jag ska titta på Kanalerna i framtiden, det kan vara en bättre strategi!)..............................................................................................
Den använder också en TaskCompletionSource
för att spåra översättningsuppgiftens status.
Inom bearbetningstjänsten (som vi återigen kommer att täcka senare) sätter vi resultatet av TaskCompletionSource
till resultatet av översättningsuppgiften.
var translatedMarkdown =
await markdownTranslatorService.TranslateMarkdown(translateModel.OriginalMarkdown,
translateModel.Language, cancellationToken);
tcs.SetResult(new TaskCompletion(translatedMarkdown, translateModel.Language, true, DateTime.Now));
}
Genom att göra detta kan vi "spola" statusen för de översättningsuppgifter vi lagrat i vår cache och ge användaren feedback om status för översättningen. Detta kan ta flera minuter beroende på trafiken och längden på markdown-filen.
Du har redan sett att vi ställer in en webbläsare cookie för dig. Detta används för att identifiera dig och dina översättningsuppgifter. Efter att du lämnat in en översättning kommer vi att använda HTMX röstning för att slå denna åtgärd som helt enkelt returnerar översättningar för dig.
[HttpGet]
[Route("get-translations")]
public IActionResult GetTranslations()
{
var userId = Request.GetUserId(Response);
var tasks = translateCacheService.GetTasks(userId);
var translations = tasks;
return PartialView("_GetTranslations", translations);
}
När du har en lista över översättningar och deras status kan du använda detta för att välja en översättning och få den att visas i Markdown editor. Detta använder denna API-slutpunkt för att få uppgiften;
[HttpGet]
[Route("get-translation/{taskId}")]
public Results<JsonHttpResult<TaskCompletion>, BadRequest<string>> GetTranslation(string taskId)
{
var userId = Request.GetUserId(Response);
var tasks = translateCacheService.GetTasks(userId);
var translationTask = tasks.FirstOrDefault(t => t.TaskId == taskId);
if(translationTask?.Task?.Status != System.Threading.Tasks.TaskStatus.RanToCompletion)
{
return TypedResults.BadRequest<string>("Task not completed");
}
return TypedResults.Json(translationTask.Task.Result);
}
Fortfarande tidiga dagar när jag bygger ut detta men jag är glad att se vart det går. Jag täcker bakgrundstjänsten i detalj i nästa del.