Localización
PowerPortalsPro utiliza un sistema de localización basado en JSON para todo el texto orientado al usuario. Esto incluye etiquetas de componentes, nombres de tablas y columnas, nombres de vista, mensajes de validación y cadenas específicas de la aplicación. La pila Blazor los IStringLocalizerlee; la pila de React los lee a través del useT() gancho. Ambas pilas consumen la MISMA forma JSON — las cadenas creadas una vez dentro app.en.json impulsan ambas pilas (y cualquier archivo de recurso compartido, como cadenas derivadas tables.* de metadatos de Dataverse, fluye automáticamente hacia ambas).
Archivos de localización
La localización se impulsa mediante archivos JSON colocados en un localization directorio dentro del proyecto del servidor. Los archivos siguen la convención name.{culture}.json de nombres (por ejemplo, app.en.json, app.fr.json). La misma forma JSON controla tanto la pila React (obtenida en tiempo de ejecución por el proveedor localizador) como la pila Blazor (leída al inicio por IStringLocalizer).
// app.en.json
{
"app": {
"navigation": {
"home": "Home",
"contacts": "Contacts"
}
}
}
Reaccionar
En el lado de React, el localizador se monta automáticamente — <PowerPortalsProProvider> incluye un <DefaultLocalizerProvider> que recupera el paquete del entorno activo desde /localizations/... el montaje y en cada cambio de localización. Envuelve el proveedor con un <LocaleProvider> estado de cambio de localización explícito (detectado automáticamente desde la ruta de la URL, ASP.NET la cookie de cultura o navigator.language por defecto). Las cadenas que el usuario ve durante la breve ventana antes de que el paquete se resuelva se renderizan como sus teclas en bruto; El proveedor cambia las cadenas reales tan pronto como el traer aterriza.
// App.tsx — bootstrap mínimo. PowerPortalsProProvider monta automáticamente
// DefaultLocalizerProvider así que useT() funciona en todos los descendientes.
import { LocaleProvider, PowerPortalsProProvider } from '@powerportalspro/react';
export function App() {
return (
<LocaleProvider>
<PowerPortalsProProvider>
<Routes />
</PowerPortalsProProvider>
</LocaleProvider>
);
}
Blazor
Registra los directorios de localización en los Program.cs servidores mediante AddLocalizationDirectory. El framework los lee al inicio, y respaldan todos IStringLocalizer / IStringLocalizer<T> resueltos a través de DI.
// Program.cs
builder.Services.AddPowerPortalsPro(options =>
{
options.AddLocalizationDirectory("localization");
options.AddLocalizationDirectory("_content/MyApp.Client/localization");
});
Archivos de localización HTML
Para contenido más extenso como plantillas de correo electrónico, puedes usar archivos HTML independientes en lugar de incrustar cadenas HTML en JSON. El nombre del archivo codifica la ruta completa de la clave de localización y la cultura, usando el formato {key-path}.{culture}.html.
Cada segmento separado por puntos del nombre del archivo se mapea a un nivel en la jerarquía de claves de localización. El penúltimo segmento es el código de cultivo (por ejemplo, en, fr). Coloque estos archivos en los mismos directorios de localización registrados con AddLocalizationDirectory.
Por ejemplo, la siguiente estructura de archivos:
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
El archivo emails.signup-confirmation.body.en.html se asigna a la clave emails.signup-confirmation.body de localización de la en cultura. Esto equivale a tener el contenido HTML como un valor de cadena en tu archivo JSON en esa ruta de clave.
// Recuperar el contenido HTML usando la clave de localización
var emailBody = _localizer["emails.signup-confirmation.body"];
Recupera el contenido usando la misma IStringLocalizer clave que corresponde al nombre del archivo. El contenido HTML se devuelve como una cadena localizada y puede renderizarse con ToMarkupString().
Tablas y etiquetas de columnas
Los nombres de visualización de tablas y columnas, descripciones y etiquetas de vistas se resuelven automáticamente a partir de los archivos de localización usando la convención tables.{tableName}.label, tables.{tableName}.columns.{columnName}.label, y 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 defecto, el framework carga metadatos para cada tabla en tu entorno Dataverse. Para cualquier portal que soporte varios idiomas, deberías configurar
LocalizeAllAvailableTables = falsey registrar solo las tablas que realmente usas —AddTableToLocalizecargar cada tabla ralentiza el calentamiento de inicio y llena la caché de cadenas con etiquetas que nunca mostras (un coste pagado por cada idioma instalado). Registra todas las tablas cuyas etiquetas aparezcan en la interfaz (cuadrículas, formularios, subcuadras, gráficos), ya que una tabla mostrada que no está localizada renderiza sus claves en bruto en lugar de etiquetas.account,contact, yadx_externalidentityse incluyen por defecto.
Ver etiquetas
Las etiquetas de vista en el desplegable selector de vistas de cuadrícula se localizan bajo tables.{tableName}.views.{viewId}.label, donde {viewId} es el GUID de la vista guardada de Dataverse (sin férucolas, minúsculas). Esto se aplica tanto MainGrid a los selectores de vistas como SubGrid a los de vista.
// En 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 vistas personalizadas definidas mediante
CustomViewDefinitions, se aplica la misma convención: usar el GUID de la vista personalizada como clave. Si no se encuentra ninguna etiqueta localizada, el nombre de la vista a partir de losGridViewDefinitionmetadatos o Dataverse se utiliza como respaldo.
Ver cabeceras de columna
Los encabezados de columna que se muestran en cuadrículas se resuelven usando un patrón de respaldo. El sistema primero busca una etiqueta de columna específica para la vista en tables.{tableName}.views.{viewId}.columns.{columnName}.label. Si no se encuentra, vuelve a la etiqueta de columna a nivel de tabla en tables.{tableName}.columns.{columnName}.label. Las descripciones emergentes siguen el mismo patrón usando .description en lugar de .label.
Esto te permite sobrescribir el encabezado de una columna para una vista específica sin afectar su etiqueta en otras vistas o editores.
// Anular una cabecera de columna para una vista 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 columnas de entidades enlazadas (por ejemplo
contact.emailaddress1, ), el nombre de columna en la clave utiliza el formato con prefijo de alias. Si no se encuentra ninguna etiqueta específica de la vista, el sistema construye una etiqueta a partir del nombre de la columna enlazada y la etiqueta de su columna principal (por ejemplo, "Correo electrónico (Contacto Principal)").
Etiquetas de elección (conjunto de opciones)
Las etiquetas de opciones de la columna de elección se localizan de forma diferente dependiendo de si el conjunto de opciones es de alcance de tabla o global.
Elecciones con alcance de tabla
Las elecciones con alcance de tabla se localizan bajo tables.{tableName}.choices.{choiceLogicalName}.values.{value}.label. El nombre lógico de elección está precedido por el nombre de la tabla (por ejemplo, account_accountcategorycode).
// En tables.en.json — decisiones con alcance de tabla
{
"tables": {
"account": {
"choices": {
"account_accountcategorycode": {
"label": "Category",
"values": {
"1": { "label": "Preferred Customer" },
"2": { "label": "Standard" }
}
}
}
}
}
}
Elecciones globales
Las elecciones globales (conjuntos de opciones compartidos entre varias tablas) se localizan a nivel raíz bajo choices.{choiceLogicalName}.values.{value}.label, fuera de cualquier sección de tabla.
// En tables.en.json — elecciones globales (a nivel raíz, fuera de las "tablas")
{
"choices": {
"powerpagelanguages": {
"label": "Preferred Language",
"values": {
"1033": { "label": "English" },
"1036": { "label": "French" },
"1031": { "label": "German" }
}
}
}
}
Nota
Los
ChoiceEditcomponentes yMultiSelectChoiceEditresuelven automáticamente las etiquetas de elección desde la ubicación correcta según laIsGlobalpropiedad de los metadatos de la columna.
Inyección del localizador
Hay dos formas de inyectar el localizador de cadenas:
IStringLocalizer— Accede a todas las claves de localización globalmente. Úsalo para etiquetas de tablas, cadenas compartidas o cuando necesites elGetPrefixedLocalizermétodo.IStringLocalizer<T>— Asignado a un tipo específico de componente. Las claves se resuelven en relación con la ruta del espacio de nombres del componente en el archivo JSON (por ejemplo,components.{Namespace}.{ComponentName}.{key}).
// Gancho global de localizador — recupera el localizador activo y devuelve su
// 't' funcionan directamente. Identidad estable por renderizado; En efecto, Safe, Deps.
import { useT, usePrefixedT } from '@powerportalspro/react';
function MyComponent() {
const t = useT();
const label = t('app.navigation.home');
// Las búsquedas con alcance de componentes en React son azúcar sobre un prefijo fijo. El
// el paquete sigue conteniendo 'componentes. <FullTypeName>. <key>' forma —</key></FullTypeName>
// Pasa ese espacio de nombres completo a usePrefixedT y llama solo con la clave.
const componentT = usePrefixedT(
'components.MyApp.Pages.MyComponent',
);
const title = componentT('title'); // → componentes. MyApp.Pages.MyComponent.title
return <h1>{title}</h1>;
}// Localizador global — acceder a cualquier clave
[Inject]
private IStringLocalizer _localizer { get; set; } = null!;
// Localizador con alcance de componentes — las claves se resuelven en relación con el espacio de nombres del componente
[Inject]
private IStringLocalizer<MyComponent> _localizer { get; set; } = null!;Claves con alcance de componentes
Al usar IStringLocalizer<T>, las claves se resuelven en función del espacio de nombres y del nombre de clase del componente. Por ejemplo, un componente en Pages.Editors.TextEdit.TextEditDemoPage resuelve claves desde components.{AssemblyName}.Pages.Editors.TextEdit.TextEditDemoPage.{key} en el JSON.
// En app.en.json — claves para un componente en Pages/Editors/TextEdit/TextEditDemoPage
{
"components": {
"MyApp.Client": {
"Pages": {
"Editors": {
"TextEdit": {
"TextEditDemoPage": {
"title": "TextEdit",
"description": "A single-line text input."
}
}
}
}
}
}
}
// usePrefixedT fija el espacio de nombres completo UNA VEZ, así que los sitios de llamada solo repiten
// La llave de seguimiento. Resolución en contra
// 'componentes. MiApp.Cliente.Páginas.Editores.TextoEditarTextoEditarPáginaDemo.título'
// en el paquete.
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') }} />
</>
);
}<!-- En el componente -->
@inject IStringLocalizer<TextEditDemoPage> _localizer
<h1>@_localizer["title"]</h1>
<p>@_localizer["description"].ToMarkupString()</p>Localizador prefijo
Úsalo GetPrefixedLocalizer para crear un sublocalizador que automáticamente anteponga un prefijo a todas las búsquedas clave. Esto es útil para evitar prefijos repetitivos en componentes que utilizan muchas teclas de la misma sección.
// usePrefixedT(prefix) devuelve una función t con cada tecla auto-pendiente
// con '${prefijo}.'. Memoizado por valor de prefijo para que sea estable en todos los aspectos
// renders (seguros en efectos de deps).
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 /* ... */;
}// Sin prefijo — repetitivo
var home = _localizer["app.navigation.home"];
var contacts = _localizer["app.navigation.contacts"];
// Con prefijo — cleaner
var navLocalizer = _localizer.GetPrefixedLocalizer("app.navigation");
var home = navLocalizer["home"];
var contacts = navLocalizer["contacts"];HTML en cadenas localizadas
Las cadenas localizadas pueden contener marcado HTML. Usa el método de ToMarkupString() extensión para renderizarlos como MarkupString en las plantillas de Razor.
// dangerouslySetInnerHTML renderiza la cadena localizada como HTML — el
// Value es un marcado confiable ya que lo creaste en el paquete.
<p dangerouslySetInnerHTML={{ __html: t('description') }} /><!-- Renderiza marcado HTML a partir de una cadena localizada -->
<p>@_localizer["description"].ToMarkupString()</p>Encontrar la mejor pareja
Lo usas FindLocalizedString para buscar la primera clave coincidente de una lista de candidatos. Esto es útil para patrones de respaldo donde quieres probar primero una clave específica y luego volver a una más general.
// 't()' devuelve la clave en bruto cuando falta una cadena, así que 't(tecla) !== clave'
// Es la comprobación de "¿Se resolvió esta clave?" sobre la que construyes una caminata de respaldo.
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`,
);// Prueba primero una clave específica y luego vuelve a la general
var label = _localizer.FindLocalizedString(
$"tables.{tableName}.columns.{columnName}.label",
$"tables.{tableName}.label");En el lado de React, useT() devuelve la clave en bruto cuando falta una cadena — t(key) !== key es la comprobación "¿se resolvió esta clave?" sobre la que construyes un recorrido de respaldo. Envuelve el patrón en un asistente cuando lo necesites en varios puntos de llamada.
Interpolación de argumentos
Las cuerdas pueden incluir posicionales {0}, {1}, ... marcadores de posición que se sustituyen en el momento de la consulta. Blazor pasa args como un array de params a IStringLocalizer; React los pasa como el segundo argumento a t(). Los especificados opcionales de formato dentro de un marcador de posición ({0:N0}, {1:yyyy-MM-dd}) son respetados únicamente por la tubería de String.Format Blazor; para React, preformatear con Intl.NumberFormat / Intl.DateTimeFormat antes de pasar el valor en.
// En app.en.json
{
"app": {
"welcome": "Welcome back, {0}! You have {1} unread messages."
}
}
// Args pasó como segundo argumento a t() — posicional, indexado por '{0}'.
const greeting = t('app.welcome', [userName, unreadCount]);// Args transmitidos mediante array de parámetros — el indexador de IStringLocalizer los acepta.
var greeting = _localizer["app.welcome", userName, unreadCount];Carga Ansiosa (React)
En caso de fallo en caché, la pila React recupera perezosamente el paquete propietario por recurso (un lote rebotado por ~75 ms) y vuelve a renderizar el componente consumidor cuando las cadenas aterrizan. Para eliminar el breve destello de las claves en bruto, declara los prefijos que una página necesitará al principio mediante useLocalization([...]), o espera el renderizado hasta que se resuelvan mediante el envoltorio de nivel <LocalizationBoundary> superior. Consulta la página de LocalizationBoundary para el patrón completo.
// Carga con entusiasmo los paquetes por recurso de una página desde el principio para evitar un
// Un destello de teclas en bruto al navegar. Tokens que ya existen
// los buscados (o en vuelo) se deduplican silenciosamente, así que llamar desde dos
// los componentes en la misma página producen un total de ida y vuelta.
import { useLocalization } from '@powerportalspro/react';
function AccountFormPage() {
useLocalization(['tables.account', 'tables.contact']);
return /* ... forma... */;
}
IStringLocalizer Interface
Propiedades
Nombre | Tipo | Default | Descripción |
|---|---|---|---|
Item | LocalizedString |
ItemMétodos
Nombre | Parámetros | Tipo | Descripción |
|---|---|---|---|
FindLocalizedString | string[] keys | LocalizedString | Devuelve la primera coincidencia válida basada en las claves proporcionadas. |
GetAllStrings | bool includeParentCultures | IEnumerable<LocalizedString> | |
GetPrefixedLocalizer | string prefix | IPrefixedStringLocalizer | Devuelve un nuevo Localization.IPrefixedStringLocalizer que precede el prefijo dado a todas las búsquedas clave. |
FindLocalizedStringGetAllStringsGetPrefixedLocalizerLocalization.IPrefixedStringLocalizer que precede el prefijo dado a todas las búsquedas clave.