Email i18n Pattern¶
Type-safe translations for Blazor email templates using typed records.
Structure¶
Integrations/Email/
├── Models/
│ └── EmailConfirmationEmail.cs # Email data model
├── Templates/
│ └── EmailConfirmationTemplate.razor
└── Translations/
├── IEmailStrings.cs # Interface
├── EmailStrings.cs # Static factory
├── EnglishEmailStrings.cs # English translations
├── CzechEmailStrings.cs # Czech translations
└── Records/
├── EmailConfirmationStrings.cs
├── PasswordResetStrings.cs
└── CommonStrings.cs
String Records¶
Each email type has a record defining its translatable strings:
public sealed record EmailConfirmationStrings(
string TitleInitial,
string TitleChange,
Func<string, string> BodyInitial, // Typed placeholder
Func<string, string> BodyChange,
string Button,
string AdminNote,
string IgnoreNoteInitial,
string IgnoreNoteChange);
Language Implementation¶
public sealed class EnglishEmailStrings : IEmailStrings
{
public EmailConfirmationStrings EmailConfirmation { get; } = new(
TitleInitial: "Confirm your email address",
TitleChange: "Confirm your email change",
BodyInitial: validity => $"Thank you for registering. The link is valid for {validity}.",
BodyChange: validity => $"We received a request to change your email. The link is valid for {validity}.",
Button: "Confirm Email",
AdminNote: "Your account will be submitted for administrator approval.",
IgnoreNoteInitial: "If you didn't create an account, you can safely ignore this email.",
IgnoreNoteChange: "If you didn't request this change, you can safely ignore this email.");
}
Static Factory¶
public static class EmailStrings
{
private static readonly IEmailStrings English = new EnglishEmailStrings();
private static readonly IEmailStrings Czech = new CzechEmailStrings();
public static IEmailStrings For(Language language) => language switch
{
Language.Czech => Czech,
_ => English
};
}
Template Usage¶
<EmailLayout Language="@(Model?.Language ?? Language.English)">
<h2>@S.EmailConfirmation.TitleInitial</h2>
<p>@S.EmailConfirmation.BodyInitial(Model?.ValidityText ?? "")</p>
<EmailButton Text="@S.EmailConfirmation.Button" />
</EmailLayout>
@code {
[Parameter]
public EmailConfirmationEmail? Model { get; set; }
private IEmailStrings S => EmailStrings.For(Model?.Language ?? Language.English);
}
Email Model Subject¶
public sealed record EmailConfirmationEmail(...) : EmailRequest(...)
{
public override string GetSubject()
{
var s = EmailStrings.For(Language).EmailConfirmation;
return IsInitialRegistration ? s.TitleInitial : s.TitleChange;
}
}
Pluralization¶
Handle language-specific plural forms inside the translation class:
public sealed class CzechEmailStrings : IEmailStrings
{
public RegistrationExpiringStrings RegistrationExpiring { get; } = new(
Title: "Vaše registrace brzy vyprší",
Body: days => $"Registrace vyprší za {days} {PluralDays(days)}.",
...);
private static string PluralDays(int count) => count switch
{
1 => "den",
>= 2 and <= 4 => "dny",
_ => "dní"
};
}
Adding a New Language¶
- Create
GermanEmailStrings.csimplementingIEmailStrings - Add to factory:
Adding a New Email Type¶
- Create record in
Records/NewEmailStrings.cs - Add property to
IEmailStringsinterface - Implement in
EnglishEmailStringsandCzechEmailStrings - Use in template via
S.NewEmail.Title
Benefits¶
| Feature | Benefit |
|---|---|
| Compile-time safety | Missing translation = build error |
| Typed placeholders | Func<string, string> prevents missing args |
| IDE support | Autocomplete, find usages, rename |
| Testable | Mock IEmailStrings for unit tests |
| Side-by-side | All English in one file for easy review |
Related¶
- i18n Pattern (Client) — Frontend translations