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
Por padrão, o framework carrega metadados para cada tabela no seu ambiente Dataverse. Para qualquer portal que suporte múltiplos idiomas, você deve definir
LocalizeAllAvailableTables = falsee registrar apenas as tabelas que você realmente usaAddTableToLocalize— carregar todas as tabelas desacelera o aquecimento inicial e preenche o cache de strings com etiquetas que você nunca exibe (um custo pago por idioma instalado). Registre todas as tabelas cujos rótulos aparecem na interface (grades, formulários, subgrades, gráficos), já que uma tabela exibida que não está localizada renderiza suas chaves brutas em vez de rótulos.account,contact, eadx_externalidentitysão incluídos por padrão.
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 —</key></FullTypeName>
// 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 Localization.IPrefixedStringLocalizer que antepõe o prefixo dado a todas as consultas chave. |
FindLocalizedStringGetAllStringsGetPrefixedLocalizerLocalization.IPrefixedStringLocalizer que antepõe o prefixo dado a todas as consultas chave.