Translating HTML Attributes
Some text in Blazor applications is not rendered as normal page content.
For example, text inside HTML attributes such as:
placeholdertitlealtaria-labelaria-describedbyvalue
These attributes cannot always be translated with TranslatableText, because they are not rendered as child content.
For these cases, you can use TranslationsCollector directly.
Example: translating an input placeholder
@implements IDisposable
<input class="text-sm border rounded-lg px-3 py-1.5 w-full sm:w-64"
placeholder="@Placeholder" />
@code {
[CascadingParameter]
public TranslationsCollector? Collector { get; set; }
private bool _subscribed;
private string Placeholder =>
Collector?.GetText("Employees.SearchPlaceholder", "Search employees…")
?? "Search employees…";
protected override void OnParametersSet()
{
if (Collector is null)
{
return;
}
Collector.RegisterText("Employees.SearchPlaceholder", "Search employees…");
if (!_subscribed)
{
Collector.TranslationsChanged += OnTranslationsChanged;
_subscribed = true;
}
}
private void OnTranslationsChanged()
=> InvokeAsync(StateHasChanged);
public void Dispose()
{
if (Collector is not null)
{
Collector.TranslationsChanged -= OnTranslationsChanged;
}
}
}
How it works
The component does three things:
- Registers the attribute text with
TranslationsCollector. - Reads the translated value using
Collector.GetText(...). - Re-renders the component when translations change.
Collector.RegisterText("Employees.SearchPlaceholder", "Search employees…");
This tells Translazor that this text should be translated and cached.
Collector.GetText("Employees.SearchPlaceholder", "Search employees…")
This returns the translated value if available. If the translation is missing, Translazor uses the fallback text.
Why StateHasChanged is needed
Attribute values are calculated by Blazor when the component renders.
When the selected language changes, the component needs to re-render so the translated attribute value can be applied.
That is why the component subscribes to:
Collector.TranslationsChanged += OnTranslationsChanged;
And then calls:
InvokeAsync(StateHasChanged);
Always unsubscribe
If your component subscribes to TranslationsChanged, it should also unsubscribe when the component is disposed.
public void Dispose()
{
if (Collector is not null)
{
Collector.TranslationsChanged -= OnTranslationsChanged;
}
}
This avoids memory leaks and prevents disposed components from receiving translation update events.
Translating multiple attributes
You can register and read multiple attributes in the same component.
<input placeholder="@SearchPlaceholder"
title="@SearchTitle"
aria-label="@SearchAriaLabel" />
@code {
[CascadingParameter]
public TranslationsCollector? Collector { get; set; }
private bool _subscribed;
private string SearchPlaceholder =>
Collector?.GetText("Employees.SearchPlaceholder", "Search employees…")
?? "Search employees…";
private string SearchTitle =>
Collector?.GetText("Employees.SearchTitle", "Search employees by name or email")
?? "Search employees by name or email";
private string SearchAriaLabel =>
Collector?.GetText("Employees.SearchAriaLabel", "Search employees")
?? "Search employees";
protected override void OnParametersSet()
{
if (Collector is null)
{
return;
}
Collector.RegisterText("Employees.SearchPlaceholder", "Search employees…");
Collector.RegisterText("Employees.SearchTitle", "Search employees by name or email");
Collector.RegisterText("Employees.SearchAriaLabel", "Search employees");
if (!_subscribed)
{
Collector.TranslationsChanged += OnTranslationsChanged;
_subscribed = true;
}
}
private void OnTranslationsChanged()
=> InvokeAsync(StateHasChanged);
public void Dispose()
{
if (Collector is not null)
{
Collector.TranslationsChanged -= OnTranslationsChanged;
}
}
}
Recommended key naming
Use clear keys that describe the page and the attribute.
Employees.SearchPlaceholder
Employees.SearchTitle
Employees.SearchAriaLabel
Products.FilterPlaceholder
Contact.EmailPlaceholder
Contact.MessagePlaceholder
A useful pattern is:
PageName.ElementNameAttribute
For example:
Employees.SearchPlaceholder
Employees.SearchAriaLabel
Contact.EmailPlaceholder
Contact.SubmitButtonTitle
When to use this approach
Use TranslationsCollector directly when the text is inside an attribute.
Good examples:
<input placeholder="@Placeholder" />
<img alt="@ImageAlt" />
<button title="@ButtonTitle">
Save
</button>
<input aria-label="@SearchAriaLabel" />
For normal visible text inside the page body, use TranslatableText instead.
<TranslatableText Key="Employees.Title" Text="Employees" />