-
Notifications
You must be signed in to change notification settings - Fork 0
PT BR Mensagens e culturas
O texto dos erros fica em catálogos JSON, um por cultura. O C# guarda os metadados — código, kind, argumentos — e nunca a redação.
Um objeto JSON plano mapeando código de erro para template de mensagem:
{
"not_found": "{resource} '{id}' não foi encontrado.",
"conflict": "Conflito em {resource}.",
"validation": "{field} é inválido.",
"unexpected": "Ocorreu um erro inesperado."
}Os onze códigos padrão — not_found, gone, conflict, validation, bad_request, unauthorized, forbidden, precondition_failed, unprocessable, too_many_requests, unexpected — cobrem todas as factories nativas. Adicione uma entrada por código customizado que você introduzir com Error.Custom.
builder.Services.AddOffside(options =>
{
options.AddJson(CultureInfo.InvariantCulture, File.ReadAllText("errors/errors.json"));
options.AddJson(new CultureInfo("pt-BR"), File.ReadAllText("errors/errors.pt-BR.json"));
options.AddJson(new CultureInfo("es"), File.ReadAllText("errors/errors.es.json"));
});AddJson recebe o conteúdo do catálogo, não um caminho. Há também uma sobrecarga com Stream para recursos embutidos:
options.AddJson(CultureInfo.InvariantCulture,
typeof(Program).Assembly.GetManifestResourceStream("MyApp.errors.json")!);Os catálogos são parseados uma vez, no registro. Um arquivo malformado falha na inicialização, não na primeira requisição que der erro.
Um catálogo de cultura invariante é obrigatório. Sem ele, AddOffside lança InvalidOperationException. Ele é o fallback final, então todo código deveria aparecer nele.
A resolução tenta três buscas em ordem e para na primeira que acertar:
- A cultura exata —
pt-BR - O pai dela —
pt - O catálogo invariante
Assim um catálogo pt serve tanto pt-BR quanto pt-PT, e você traduz os específicos só onde a redação realmente difere. Se nenhum catálogo define o código, o resolver devolve o próprio código — a resposta continua bem formada e a entrada faltante fica visível em vez de vir em branco.
Em um host ASP.NET Core, a cultura vem do header Accept-Language a menos que você passe uma explicitamente. Veja Culturas.
Os tokens do template são {name}, preenchidos a partir de Error.Arguments:
Error.NotFound("order", 42)
// argumentos: resource = "order", id = 42{ "not_found": "{resource} '{id}' não foi encontrado." }order '42' não foi encontrado.
Três comportamentos a conhecer:
-
Um token sem argumento correspondente permanece literal.
Error.NotFound("order")contra o template acima produzorder '{id}' não foi encontrado.— um argumento nulo é pulado, não zerado. Visível, não silencioso. -
Os valores são formatados com
InvariantCulture. Números e datas saem estáveis independentemente da cultura da requisição. Formate você mesmo antes de passar, se precisar de saída sensível à localidade. -
Isto é substituição de token, não
string.Format. Não existe{0}, nem especificadores de formato como{amount:C}, nem escaping — chaves sem argumento correspondente sobrevivem como escritas.
- Copie
errors/errors.jsonparaerrors/errors.<cultura>.json. - Traduza os valores. Deixe as chaves e os
{tokens}intactos. - Registre com
options.AddJson(new CultureInfo("<cultura>"), ...).
{
"not_found": "{resource} '{id}' was not found.",
"conflict": "Conflict on {resource}.",
"validation": "{field} is invalid.",
"unexpected": "An unexpected error occurred."
}Um catálogo traduzido não precisa estar completo. O que ele omitir cai para a cultura pai e depois para o catálogo invariante, então dá para publicar uma tradução parcial e completá-la com o tempo.
offside init escreve um catálogo em inglês e um em português do Brasil como ponto de partida — veja a página do CLI.
Instale a integração opcional quando o Azure App Configuration for a fonte dos catálogos:
dotnet add package Offside.AzureAppConfiguration
dotnet add package Microsoft.Extensions.Configuration.AzureAppConfiguration
dotnet add package Microsoft.Azure.AppConfiguration.AspNetCore # refresh no ASP.NET CoreO pacote lê uma seção do IConfiguration já montado pelo host; ele não conecta ao Azure nem escolhe labels. A seção padrão é Errors, seguida da cultura e do código da mensagem:
Errors:default:not_found = missing {resource}
Errors:pt-BR:not_found = nao encontrado {resource}
default é obrigatório e o fallback final. Também é possível armazenar um catálogo em Errors:pt-BR com content type application/json; o Azure o achata para a mesma hierarquia:
{ "not_found": "nao encontrado {resource}" }Configure o Azure no host, selecione Errors:* e habilite o refresh. Registre este resolver em vez de AddOffside:
using Azure.Identity;
using Offside.AzureAppConfiguration;
builder.Configuration.AddAzureAppConfiguration(options => options
.Connect(new Uri(builder.Configuration["AppConfig:Endpoint"]!), new DefaultAzureCredential())
.Select("Errors:*")
.ConfigureRefresh(refresh => refresh.RegisterAll()));
builder.Services.AddAzureAppConfiguration();
builder.Services.AddOffsideAzureAppConfiguration(builder.Configuration);
var app = builder.Build();
app.UseAzureAppConfiguration();O resolver lê a configuração a cada busca, então um refresh concluído afeta a próxima resposta sem reiniciar a aplicação. Em workers, o host deve disparar o refresher; labels, credenciais, seletores e intervalo de refresh continuam sendo responsabilidade do host. Para outra raiz, passe options => options.SectionName = "MyErrors".
AddOffside registra um JsonErrorMessageResolver como o singleton IErrorMessageResolver. Para buscar mensagens de outro lugar — um banco, assemblies satélite de recursos, um serviço de tradução — implemente a interface e registre-a no lugar:
public sealed class ResxErrorMessageResolver : IErrorMessageResolver
{
public string GetMessage(Error error, CultureInfo culture) =>
Messages.ResourceManager.GetString(error.Code, culture) ?? error.Code;
}
builder.Services.AddSingleton<IErrorMessageResolver, ResxErrorMessageResolver>();Nesse caso não chame AddOffside — ele registraria o resolver JSON ao lado do seu. Devolver error.Code para um código desconhecido é a convenção que vale manter: degrada para algo diagnosticável em vez de uma string vazia.
Código e docs/ são canônicos · Code and docs/ are canonical · Base dbca345372483477097385eb43d850484b00ec13 · 2026-08-22 · Repositório / Repository