BstTranslations 10.0.14
BstTranslations
Property-based translation system for ASP.NET Core. You define one C# class with one property per translatable string; values are loaded per culture from a backing store you control, with localizable DataAnnotations validators and an enum-translation attribute.
Setup
1. Implement ITranslation
A persisted translation row. Bring your own EF entity:
using BstTranslations.Models.Contracts;
public class Translation : ITranslation {
public int Id { get; set; }
public string Name { get; set; }
public string DefaultValue { get; set; }
public DateTimeOffset ValueUpdateTime { get; set; }
public string ValuePt { get; set; }
public string ValueEn { get; set; }
public string GetValueForCulture(string cultureCode) => cultureCode switch {
"en" => ValueEn,
_ => ValuePt ?? DefaultValue
};
}
2. Implement ITranslationsManager
Loads/persists translations from your storage (DB, file, etc.):
public class TranslationsManager : ITranslationsManager {
public Task<List<ITranslation>> GetTranslations() { ... }
public Task<ITranslation> GetTranslationByName(string name) { ... }
public Task<ITranslation> AddTranslation(string name, string value = null) { ... }
}
3. Subclass BaseTranslationsService
One property per translation key. The default value passed to T(...) is what gets stored if the key doesn't exist yet:
public class AppTranslations(IHttpContextAccessor ctx, IServiceScopeFactory sf)
: BaseTranslationsService(ctx, sf) {
public HtmlString Welcome => T("Bem-vindo");
public HtmlString GreetingWithName => T("Olá, {0}!");
public HtmlString Required_Field => T("Este campo é obrigatório");
}
4. Register
// BstLogger must already be registered.
services.AddTranslationsService<AppTranslations, TranslationsManager>(singleton: true);
singleton: true uses an in-memory cache shared across requests; false reloads per scope.
Usage
Translate in views/services
@inject AppTranslations T
<h1>@T.Welcome</h1>
<p>@T.GreetingWithName.Params(user.Name)</p>
Params() (from BstTranslations.Extensions) does string.Format on an HtmlString.
The culture is read from the culture route value (/{culture}/...). Override at runtime:
T.SetCulture("en");
T.SetDynamicCulture(true); // re-read culture from route on every T(...) call
Localizable validators
Use the Localizable* variants and pass the property name of the translation, not a literal message:
using BstTranslations.DataAnnotations.LocalizableValidators;
public class RegisterDto {
[LocalizableRequired(nameof(AppTranslations.Required_Field))]
[LocalizableMaxLength(50, nameof(AppTranslations.MaxLength_50))]
public string Name { get; set; }
[LocalizableEmailAddress(nameof(AppTranslations.Invalid_Email))]
public string Email { get; set; }
[LocalizableMinLength(8, nameof(AppTranslations.Password_TooShort))]
public string Password { get; set; }
[LocalizableCompare(nameof(Password), nameof(AppTranslations.Passwords_DoNotMatch))]
public string PasswordConfirmation { get; set; }
}
The provided IValidationAttributeAdapterProvider resolves the property name to the current translation, both server-side and in jquery.validate unobtrusive output.
Enum translations
using BstTranslations.DataAnnotations;
public enum OrderStatus {
[EnumWithTranslation(nameof(AppTranslations.Status_Pending))] Pending,
[EnumWithTranslation(nameof(AppTranslations.Status_Shipped))] Shipped,
[EnumWithTranslation(nameof(AppTranslations.Status_Delivered))] Delivered
}
@T.GetEnumTranslation(order.Status)
Sync translation keys to storage
TranslationsHelper.GetTranslationObjects reflects every property on your translations service into ITranslation rows so you can seed/upsert your store:
var rows = TranslationsHelper
.GetTranslationObjects<AppTranslations, Translation>(appTranslations);
// upsert rows via your TranslationsManager / DbContext
Call T.UpdateTranslations() to refresh the in-memory cache (singleton mode) after changes.
No packages depend on BstTranslations.
.NET 10.0
- BstHelpers (>= 10.0.14)
- BstLogger (>= 10.0.3)
- Microsoft.EntityFrameworkCore (>= 10.0.7)
| Version | Downloads | Last updated |
|---|---|---|
| 10.0.14 | 2 | 03/05/2026 |
| 10.0.13 | 3 | 23/04/2026 |
| 10.0.12 | 4 | 16/04/2026 |
| 10.0.11 | 14 | 06/04/2026 |
| 10.0.10 | 6 | 20/03/2026 |
| 10.0.9 | 4 | 20/03/2026 |
| 10.0.8 | 4 | 20/03/2026 |
| 10.0.7 | 4 | 20/03/2026 |
| 10.0.6 | 17 | 11/03/2026 |
| 10.0.5 | 13 | 10/02/2026 |
| 10.0.4 | 8 | 28/01/2026 |
| 10.0.3 | 7 | 28/01/2026 |
| 10.0.2 | 20 | 12/12/2025 |
| 10.0.1 | 14 | 11/11/2025 |
| 9.2.4 | 11 | 06/11/2025 |
| 9.2.3 | 8 | 31/10/2025 |
| 9.2.2 | 7 | 20/10/2025 |
| 9.2.1 | 7 | 20/10/2025 |
| 9.2.0 | 9 | 15/10/2025 |
| 9.1.9 | 10 | 24/09/2025 |
| 9.1.8 | 20 | 06/09/2025 |
| 9.1.7 | 12 | 26/07/2025 |
| 9.1.6 | 12 | 15/07/2025 |
| 9.1.5 | 14 | 12/06/2025 |
| 9.1.4 | 46 | 19/05/2025 |
| 9.1.3 | 15 | 14/05/2025 |
| 9.1.2 | 24 | 09/04/2025 |
| 9.1.1 | 28 | 22/03/2025 |
| 9.1.0 | 15 | 19/03/2025 |
| 9.0.9 | 15 | 19/03/2025 |
| 9.0.8 | 14 | 19/03/2025 |
| 9.0.7 | 21 | 08/03/2025 |
| 9.0.6 | 15 | 05/03/2025 |
| 9.0.5 | 27 | 13/02/2025 |
| 9.0.4 | 15 | 12/02/2025 |
| 9.0.3 | 27 | 28/01/2025 |
| 9.0.2 | 19 | 23/01/2025 |
| 9.0.1 | 20 | 16/12/2024 |