Localização
O PowerPortalsPro utiliza um sistema de localização baseado em JSON para todo o texto voltado para o usuário. Isso inclui rótulos de componentes, nomes de tabelas e colunas, nomes de visualização, mensagens de validação e strings específicas de aplicação. A pilha Blazor os lê; IStringLocalizera pilha React os lê através do useT() gancho. Ambas as pilhas consomem a MESMA forma JSON — strings criadas uma vez dentro app.en.json impulsionam ambas as pilhas (e quaisquer arquivos de recursos compartilhados, como strings derivadas tables.* de metadados do Dataverse, fluem automaticamente para ambas).
Arquivos de localização
A localização é impulsionada por arquivos JSON colocados em um localization diretório no projeto do servidor. Arquivos seguem a convenção name.{culture}.json de nomes (por exemplo, app.en.json). app.fr.json A mesma forma JSON controla tanto a pilha React (buscada em tempo de execução pelo fornecedor de localizador) quanto a pilha Blazor (lida na inicialização por IStringLocalizer).
// app.en.json
{
"app": {
"navigation": {
"home": "Home",
"contacts": "Contacts"
}
}
}
React
No lado do React, o localizador é montado automaticamente por <PowerPortalsProProvider> — ele inclui um <DefaultLocalizerProvider> que busca o pacote do local ativo a /localizations/... partir do montado e em cada mudança de local. Envolva o provedor com um <LocaleProvider> quando você quiser um estado explícito de troca de local (detectado automaticamente pelo caminho da URL, ASP.NET cookie de cultura ou navigator.language por padrão). Strings que o usuário vê durante a breve janela antes da resolução do bundle renderizam como suas chaves brutas; O fornecedor troca as cadeias reais assim que a busca chega.
// App.tsx — bootstrap mínimo. PowerPortalsProProvider monta automaticamente
// DefaultLocalizerProvider então useT() funciona em todos os descendentes.
import { LocaleProvider, PowerPortalsProProvider } from '@powerportalspro/react';
export function App() {
return (
<LocaleProvider>
<PowerPortalsProProvider>
<Routes />
</PowerPortalsProProvider>
</LocaleProvider>
);
}
Blazor
Registre os diretórios de localização nos servidores Program.cs via AddLocalizationDirectory. O framework os lê na inicialização, e eles fazem backup de tudo IStringLocalizer / IStringLocalizer<T> resolvido via DI.
// Program.cs
builder.Services.AddPowerPortalsPro(options =>
{
options.AddLocalizationDirectory("localization");
options.AddLocalizationDirectory("_content/MyApp.Client/localization");
});
Arquivos de localização HTML
Para conteúdos mais longos, como modelos de e-mail, você pode usar arquivos HTML independentes em vez de incorporar strings HTML em JSON. O nome do arquivo codifica o caminho completo da chave de localização e a cultura, usando o formato {key-path}.{culture}.html.
Cada segmento separado por pontos do nome do arquivo mapeia para um nível na hierarquia de chaves de localização. O penúltimo segmento é o código de cultura (por exemplo, en, fr). Coloque esses arquivos nos mesmos diretórios de localização registrados em AddLocalizationDirectory.
Por exemplo, a seguinte estrutura de arquivo:
localization/
app.en.json
app.fr.json
emails.signup-confirmation.body.en.html
emails.signup-confirmation.body.fr.html
emails.password-reset.body.en.html
O arquivo emails.signup-confirmation.body.en.html mapeia para a chave emails.signup-confirmation.body de localização da en cultura. Isso é equivalente a ter o conteúdo HTML como um valor de string no seu arquivo JSON nesse caminho de chave.
// Recupere o conteúdo HTML usando a chave de localização
var emailBody = _localizer["emails.signup-confirmation.body"];
Recupere o conteúdo usando a mesma IStringLocalizer chave correspondente ao nome do arquivo. O conteúdo HTML é retornado como uma string localizada e pode ser renderizado com ToMarkupString().
Etiquetas de Tabelas e Colunas
Nomes de exibição de tabelas e colunas, descrições e etiquetas de visualização são automaticamente resolvidos a partir dos arquivos de localização usando a convenção tables.{tableName}.label, tables.{tableName}.columns.{columnName}.label, e tables.{tableName}.views.{viewId}.label.
// tables.en.json
{
"tables": {
"account": {
"label": "Account",
"collectionLabel": "Accounts",
"columns": {
"name": {
"label": "Account Name",
"description": "The name of the account."
}
},
"views": {
"00000000-0000-0000-0000-000000000001": {
"label": "Active Accounts"
}
}
}
}
}
Nota
Apenas as tabelas que você registra são localizadas. Uma tabela exibida que não está registrada renderiza chaves brutas, como
tables.contoso_project.columns.contoso_name.labelem vez de seus rótulos, então registre todas as tabelas cujos rótulos aparecem na interface: grades, formulários, subgrades e gráficos.
Escolha de quais tabelas são localizadas
Registre tabelas em LocalizationOptions . Program.csAs listas prontas em Tables cobrem as tabelas Dataverse comumente usadas, um prefixo de publisher pega todas as tabelas que você cria sob essa publicadora e AddTableToLocalize adiciona uma tabela. Uma tabela listada que não existe no seu ambiente é pulada, então as listas podem nomear tabelas de aplicativos do Dynamics 365 que você não instalou. Os templates de projeto começam com Tables.CoreTables.
.Configure<LocalizationOptions>(options =>
{
// As tabelas mais usadas. Os modelos começam com isso.
options.AddTablesToLocalize(Tables.CoreTables);
// Cada tabela criada sob o seu editor. O sublinhado final é opcional.
options.AddTablesToLocalizeByPublisherPrefix("new_");
// Uma mesa de cada vez.
options.AddTableToLocalize("contoso_project");
})
As listas em:Tables
CoreTables: contas, contatos e endereços; usuários, equipes, unidades de negócios e moedas; filas e localizações de documentos do SharePoint; além de tudo nas quatro listas abaixo.ActivityTables: atividades, e-mails, ligações, tarefas, compromissos, anotações e anexos.SalesTables: leads, oportunidades, orçamentos, pedidos, faturas e o catálogo de produtos.ServiceTables: casos, artigos de conhecimento, direitos, contratos e acordos de nível de serviço.MarketingTables: campanhas, atividades e respostas a campanhas, e listas de marketing.PowerPortalsProTables: as tabelas exibidas pelas próprias páginas do Power Portals Pro. Essas são sempre localizadas, então não há necessidade de adicioná-las.
Ver Rótulos
Os rótulos da vista no seletor de grade são localizados sob tables.{tableName}.views.{viewId}.label, onde {viewId} é o GUID da visualização salva do Dataverse (sem colchetes, minúscula). Isso se aplica tanto MainGrid aos seletores quanto SubGrid aos de visualização.
// Em tables.en.json
{
"tables": {
"account": {
"views": {
"00000000-0000-0000-00aa-000010001001": {
"label": "Active Accounts"
},
"00000000-0000-0000-00aa-000010001002": {
"label": "Inactive Accounts"
},
"91732ad4-b4fe-49ff-80cd-72b280eff088": {
"label": "All Contacts & Accounts"
}
}
}
}
}
Nota
Para visualizações personalizadas definidas via
CustomViewDefinitions, a mesma convenção se aplica — use o GUID da visualização personalizada como chave. Se nenhum rótulo localizado for encontrado, o nome da visualização a partir dosGridViewDefinitionmetadados do ou Dataverse é usado como recurso B.
Ver cabeçalhos de coluna
Cabeçalhos de coluna exibidos em grades são resolvidos usando um padrão de recurso. O sistema primeiro procura um rótulo de coluna específico para visualização em tables.{tableName}.views.{viewId}.columns.{columnName}.label. Se não for encontrado, ele volta ao rótulo da coluna em nível de tabela em tables.{tableName}.columns.{columnName}.label. As dicas de ferramenta seguem o mesmo padrão usando .description em vez de .label.
Isso permite que você substitua o cabeçalho de uma coluna para uma visualização específica sem afetar seu rótulo em outras visualizações ou editores.
// Sobrescreva um cabeçalho de coluna para uma visualização específica
{
"tables": {
"account": {
"views": {
"00000000-0000-0000-00aa-000010001001": {
"label": "Active Accounts",
"columns": {
"name": {
"label": "Company",
"description": "The company name for this account."
},
"contact.emailaddress1": {
"label": "Contact Email"
}
}
}
}
}
}
}
Nota
Para colunas de entidades vinculadas (por exemplo
contact.emailaddress1, ), o nome da coluna na chave usa o formato com prefixo de alias. Se não for encontrado um rótulo específico para a visualização, o sistema constrói um rótulo a partir do nome da coluna vinculada e do rótulo da coluna pai (por exemplo, "Email (Contato Primário)").
Rótulos de Escolha (Conjunto de Opções)
Os rótulos das opções das colunas de escolha são localizados de forma diferente dependendo se o conjunto de opções é com escopo de tabela ou global.
Escolhas com escopo de tabela
Escolhas com escopo de tabela são localizadas sob tables.{tableName}.choices.{choiceLogicalName}.values.{value}.label. O nome lógico da escolha é precedido pelo nome da tabela (por exemplo, account_accountcategorycode).
// Em tables.en.json — escolhas com escopo de tabela
{
"tables": {
"account": {
"choices": {
"account_accountcategorycode": {
"label": "Category",
"values": {
"1": { "label": "Preferred Customer" },
"2": { "label": "Standard" }
}
}
}
}
}
}
Escolhas Globais
Escolhas globais (conjuntos de opções compartilhados entre múltiplas tabelas) são localizadas no nível raiz sob choices.{choiceLogicalName}.values.{value}.label, fora de qualquer seção de tabela.
// Em tables.en.json — escolhas globais (nível raiz, fora das "tabelas")
{
"choices": {
"powerpagelanguages": {
"label": "Preferred Language",
"values": {
"1033": { "label": "English" },
"1036": { "label": "French" },
"1031": { "label": "German" }
}
}
}
}
Nota
Os
ChoiceEditcomponentes eMultiSelectChoiceEditresolvem automaticamente as etiquetas de escolha a partir da localização correta com base naIsGlobalpropriedade dos metadados da coluna.
Injeção do localizador
Existem duas maneiras de injetar o localizador de string:
IStringLocalizer— Acesse todas as chaves de localização globalmente. Use isso para etiquetas de tabelas, strings compartilhadas ou quando precisar doGetPrefixedLocalizermétodo.IStringLocalizer<T>— Escopado para um tipo específico de componente. As chaves são resolvidas em relação ao caminho do namespace do componente no arquivo JSON (por exemplo,components.{Namespace}.{ComponentName}.{key}).
// Gancho global do localizador — busca o localizador ativo e retorna seu
// 't' funcionam diretamente. Identidade estável por render; Na prática, DEPS seguros.
import { useT, usePrefixedT } from '@powerportalspro/react';
function MyComponent() {
const t = useT();
const label = t('app.navigation.home');
// Buscas com escopo de componentes no React são 'açúcar' sobre um prefixo fixo. O
// o feixe ainda carrega 'componentes. <FullTypeName>. <key>' forma —
// Passe esse espaço de nomes completo para o usePrefixedT e ligue só com a chave.
const componentT = usePrefixedT(
'components.MyApp.Pages.MyComponent',
);
const title = componentT('title'); // → componentes. MyApp.Pages.MyComponent.title
return <h1>{title}</h1>;
}// Localizador global — acesse qualquer chave
[Inject]
private IStringLocalizer _localizer { get; set; } = null!;
// Localizador com escopo de componente — as chaves resolvem em relação ao namespace do componente
[Inject]
private IStringLocalizer<MyComponent> _localizer { get; set; } = null!;Chaves com escopo de componentes
Ao usar IStringLocalizer<T>, as chaves são resolvidas com base no namespace e no nome da classe do componente. Por exemplo, um componente em Pages.Editors.TextEdit.TextEditDemoPage resolve chaves de components.{AssemblyName}.Pages.Editors.TextEdit.TextEditDemoPage.{key} no JSON.
// Em app.en.json — chaves para um componente em Pages/Editors/TextEdit/TextEditDemoPage
{
"components": {
"MyApp.Client": {
"Pages": {
"Editors": {
"TextEdit": {
"TextEditDemoPage": {
"title": "TextEdit",
"description": "A single-line text input."
}
}
}
}
}
}
}
// usePrefixedT fixa o namespace completo UMA VEZ, então sites de chamada só repetem
// a chave final. Resoluções contra
// 'componentes. MyApp.Client.Pages.Editors.TextEdit.TextEditDemoPage.title'
// no pacote.
import { usePrefixedT } from '@powerportalspro/react';
const STRINGS_BASE =
'components.MyApp.Client.Pages.Editors.TextEdit.TextEditDemoPage';
export function TextEditDemoPage() {
const t = usePrefixedT(STRINGS_BASE);
return (
<>
<h1>{t('title')}</h1>
<p dangerouslySetInnerHTML={{ __html: t('description') }} />
</>
);
}<!-- No componente -->
@inject IStringLocalizer<TextEditDemoPage> _localizer
<h1>@_localizer["title"]</h1>
<p>@_localizer["description"].ToMarkupString()</p>Localizador Prefixado
Use GetPrefixedLocalizer para criar um sub-localizador que automaticamente anteponha um prefixo a todas as buscas de chaves. Isso é útil para evitar prefixos repetitivos de teclas em componentes que usam muitas teclas da mesma seção.
// usePrefixedT(prefix) retorna uma função t com cada tecla auto-prependida
// com '${prefixo}.'. Memoizado por valor de prefixo para que fique estável em todos os pontos
// renderizações (deps de efeito seguros).
import { usePrefixedT } from '@powerportalspro/react';
function Nav() {
const t = usePrefixedT('app.navigation');
const home = t('home'); // → app.navigation.home
const contacts = t('contacts'); // → app.navigation.contacts
return /* ... */;
}// Sem prefixo — repetitivo
var home = _localizer["app.navigation.home"];
var contacts = _localizer["app.navigation.contacts"];
// Com prefixo — cleaner
var navLocalizer = _localizer.GetPrefixedLocalizer("app.navigation");
var home = navLocalizer["home"];
var contacts = navLocalizer["contacts"];HTML em Strings Localizadas
Strings localizadas podem conter marcação HTML. Use o método de ToMarkupString() extensão para renderizá-los como MarkupString nos templates do Razor.
// dangerouslySetInnerHTML renderiza a string localizada como HTML — o
// Valor é marcação confiável, já que você a criou no pacote.
<p dangerouslySetInnerHTML={{ __html: t('description') }} /><!-- Renderiza marcação HTML a partir de uma string localizada -->
<p>@_localizer["description"].ToMarkupString()</p>Encontrando a Melhor Combinação
Use-se FindLocalizedString para procurar a primeira chave correspondente de uma lista de candidatos. Isso é útil para padrões de recurso, onde você quer tentar uma chave específica primeiro e depois recorrer a uma mais geral.
// 't()' retorna a chave bruta quando falta uma string, então 't(chave) !== chave'
// A verificação "Essa chave resolveu?" é onde você constrói uma caminhada de recurso.
import { useT } from '@powerportalspro/react';
function resolve(t: ReturnType<typeof useT>, ...candidates: string[]): string {
for (const key of candidates) {
const value = t(key);
if (value !== key) return value;
}
return candidates[candidates.length - 1];
}
const t = useT();
const label = resolve(
t,
`tables.${tableName}.columns.${columnName}.label`,
`tables.${tableName}.label`,
);// Tente uma chave específica primeiro, depois volte para a geral
var label = _localizer.FindLocalizedString(
$"tables.{tableName}.columns.{columnName}.label",
$"tables.{tableName}.label");No lado do React, retorna useT() a chave bruta quando uma string está faltando — t(key) !== key é o teste "essa chave resolveu?" que você cria um walkback de backup. Envolva o molde em um auxiliar quando precisar em vários pontos de chamada.
Interpolação de argumentos
As cordas podem incluir posicionais {0}, {1}, ... Provisórios de posição que são substituídos no momento da consulta. Blazor passa args como um array de parâmetros para IStringLocalizer; React passa por elas como o segundo argumento para t(). Especificadores de formato opcionais dentro de um marcador de lugar ({0:N0}, {1:yyyy-MM-dd}) são respeitados apenas pelo pipeline de String.Format Blazor; para React, pré-formate com Intl.NumberFormat / Intl.DateTimeFormat antes de passar o valor em.
// Em app.en.json
{
"app": {
"welcome": "Welcome back, {0}! You have {1} unread messages."
}
}
// Args passou como o segundo argumento para t() — posicional, indexado por '{0}'.
const greeting = t('app.welcome', [userName, unreadCount]);// Args passados via array de parâmetros — o indexador do IStringLocalizer os aceita.
var greeting = _localizer["app.welcome", userName, unreadCount];Carregamento Ansioso (React)
Em caso de erro no cache, a pilha React recupera preguiçosamente o bundle por recurso que possui (um lote debounced a cada ~75 ms) e re-renderiza o componente consumidor quando as strings aterrissam. Para eliminar o breve flash das chaves brutas, declare os prefixos que uma página precisará no início via useLocalization([...]), ou espere a renderização até que eles se resolvam pelo wrapper de nível <LocalizationBoundary> superior. Veja a página LocalizationBoundary para o padrão completo.
// Carregue com entusiasmo os pacotes por recurso de uma página logo no início para evitar um
// O flash das teclas brutas ao navegar para dentro. Tokens que já existem
// buscados (ou em voo) são silenciosamente desduplicados, então chamar de dois
// componentes na mesma página produzem um total de ida e volta.
import { useLocalization } from '@powerportalspro/react';
function AccountFormPage() {
useLocalization(['tables.account', 'tables.contact']);
return /* ... forma... */;
}
IStringLocalizer Interface
Propriedades
Nome | Tipo | Padrão | Descrição |
|---|---|---|---|
Item | LocalizedString |
ItemMétodos
Nome | Parâmetros | Tipo | Descrição |
|---|---|---|---|
FindLocalizedString | string[] keys | LocalizedString | Retorna a primeira correspondência válida com base nas chaves fornecidas. |
GetAllStrings | bool includeParentCultures | IEnumerable<LocalizedString> | |
GetPrefixedLocalizer | string prefix | IPrefixedStringLocalizer | Retorna um novo IPrefixedStringLocalizer que antepõe o prefixo dado a todas as consultas chave. |
FindLocalizedStringGetAllStringsGetPrefixedLocalizerIPrefixedStringLocalizer que antepõe o prefixo dado a todas as consultas chave.