diff --git a/Apis/api/Controllers/CvMatcherController.cs b/Apis/api/Controllers/CvMatcherController.cs
index 5a5f60d..57aaeef 100644
--- a/Apis/api/Controllers/CvMatcherController.cs
+++ b/Apis/api/Controllers/CvMatcherController.cs
@@ -172,7 +172,7 @@ public sealed class CvMatcherController : ControllerBase
var attachmentPath = TryGetCachedCvPath(request.CvDocumentId);
var jobLabel = !string.IsNullOrWhiteSpace(request.JobUrl)
? request.JobUrl
- : "Manual job description";
+ : _emailSender.GetManualJobLabel(language);
var language = NormalizeLanguage(request.Language);
diff --git a/Apis/api/Services/Contracts/IEmailSender.cs b/Apis/api/Services/Contracts/IEmailSender.cs
index 8fd7502..a7ccfd2 100644
--- a/Apis/api/Services/Contracts/IEmailSender.cs
+++ b/Apis/api/Services/Contracts/IEmailSender.cs
@@ -61,5 +61,11 @@ namespace Api.Services.Contracts
/// Number of days until the job-search link expires (shown in the footer copy).
/// Rendered HTML body string.
string BuildMatchEmailBody(string cvDocumentId, JobMatchResponse result, string? jobLabel, string language, string? jobSearchLink = null, int expiryDays = 7);
+
+ ///
+ /// Returns the localised label for a manually-entered job description (no URL provided).
+ ///
+ /// Two-letter language code.
+ string GetManualJobLabel(string language);
}
}
diff --git a/Apis/api/Services/EmailApiEmailSender.cs b/Apis/api/Services/EmailApiEmailSender.cs
index 86a416f..a886ac6 100644
--- a/Apis/api/Services/EmailApiEmailSender.cs
+++ b/Apis/api/Services/EmailApiEmailSender.cs
@@ -239,4 +239,7 @@ public sealed class EmailApiEmailSender : IEmailSender
_emailTemplates.Render("email.match.subject", language,
("score", score.ToString()),
("jobLabel", jobLabel ?? "Job"));
+
+ public string GetManualJobLabel(string language) =>
+ _emailTemplates.Get("email.match.manual-job-label", language);
}
diff --git a/Apis/email-data/Migrations/20260608190611_AddManualJobLabelTemplate.Designer.cs b/Apis/email-data/Migrations/20260608190611_AddManualJobLabelTemplate.Designer.cs
new file mode 100644
index 0000000..9bbf1b6
--- /dev/null
+++ b/Apis/email-data/Migrations/20260608190611_AddManualJobLabelTemplate.Designer.cs
@@ -0,0 +1,69 @@
+//
+using System;
+using Email.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Email.Data.Migrations
+{
+ [DbContext(typeof(EmailDbContext))]
+ [Migration("20260608190611_AddManualJobLabelTemplate")]
+ partial class AddManualJobLabelTemplate
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasDefaultSchema("email")
+ .HasAnnotation("ProductVersion", "10.0.7")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("Email.Data.Entities.EmailTemplateEntity", b =>
+ {
+ b.Property("Key")
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.Property("Language")
+ .HasMaxLength(8)
+ .HasColumnType("nvarchar(8)");
+
+ b.Property("Description")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)")
+ .HasDefaultValue("");
+
+ b.Property("OperatorCopy")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)")
+ .HasDefaultValue("");
+
+ b.Property("UpdatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("datetime2")
+ .HasDefaultValueSql("SYSUTCDATETIME()");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Key", "Language");
+
+ b.ToTable("Templates", "email");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Apis/email-data/Migrations/20260608190611_AddManualJobLabelTemplate.cs b/Apis/email-data/Migrations/20260608190611_AddManualJobLabelTemplate.cs
new file mode 100644
index 0000000..6fe873f
--- /dev/null
+++ b/Apis/email-data/Migrations/20260608190611_AddManualJobLabelTemplate.cs
@@ -0,0 +1,43 @@
+using Email.Data;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Email.Data.Migrations
+{
+ ///
+ public partial class AddManualJobLabelTemplate : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.InsertData(
+ schema: MigrationConstants.SchemaName,
+ table: "Templates",
+ columns: ["Key", "Language", "Value", "Description"],
+ values: ["email.match.manual-job-label", "en", "Manual job description", "Label used in the match email subject and body when no job URL was provided"]);
+
+ migrationBuilder.InsertData(
+ schema: MigrationConstants.SchemaName,
+ table: "Templates",
+ columns: ["Key", "Language", "Value", "Description"],
+ values: ["email.match.manual-job-label", "ro", "Descriere manuală a jobului", "Etichetă folosită în subiectul și corpul emailului când nu a fost furnizat un URL de job"]);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DeleteData(
+ schema: MigrationConstants.SchemaName,
+ table: "Templates",
+ keyColumns: ["Key", "Language"],
+ keyValues: ["email.match.manual-job-label", "en"]);
+
+ migrationBuilder.DeleteData(
+ schema: MigrationConstants.SchemaName,
+ table: "Templates",
+ keyColumns: ["Key", "Language"],
+ keyValues: ["email.match.manual-job-label", "ro"]);
+ }
+ }
+}
diff --git a/Jobs/cv-search-job/Tasks/CvSearchJobTask.cs b/Jobs/cv-search-job/Tasks/CvSearchJobTask.cs
index 43337d5..f2ffb2b 100644
--- a/Jobs/cv-search-job/Tasks/CvSearchJobTask.cs
+++ b/Jobs/cv-search-job/Tasks/CvSearchJobTask.cs
@@ -198,7 +198,9 @@ public sealed class CvSearchJobTask : IJobTask
// Pre-fetched text passed directly so cv-matcher-api skips re-fetching the page
JobDescription = jobText,
// User already gave GDPR consent when they clicked the one-time job search link
- GdprConsent = true
+ GdprConsent = true,
+ // Propagate language so the LLM uses the correct language-specific prompt
+ Language = session.Language
};
var matchResult = await _matcherApi.MatchJobAsync(matchRequest, ct);