Localisation
PowerPortalsPro utilise un système de localisation JSON pour tout le texte destiné à l’utilisateur. Cela inclut les étiquettes de composants, les noms de tables et de colonnes, les noms de vues, les messages de validation et les chaînes spécifiques à chaque application. La pile Blazor les IStringLocalizerlit ; la pile Réaction les lit à travers le useT() crochet. Les deux piles consomment la MÊME forme JSON — les chaînes créées une fois dans app.en.json l’univers entraînent les deux piles (et tout fichier de ressources partagé comme les chaînes dérivées tables.* des métadonnées Dataverse circule automatiquement vers les deux).
Fichiers de localisation
La localisation est pilotée par des fichiers JSON placés dans un localization répertoire du projet serveur. Les fichiers suivent la convention name.{culture}.json de nommage (par app.en.jsonexemple , app.fr.json). La même forme JSON pilote à la fois la pile React (récupérée à l’exécution par le fournisseur localisateur) et la pile Blazor (lue au démarrage par IStringLocalizer).
// app.en.json
{
"app": {
"navigation": {
"home": "Home",
"contacts": "Contacts"
}
}
}
Réagir
Du côté React, le localisateur est monté automatiquement par <PowerPortalsProProvider> — il inclut un <DefaultLocalizerProvider> qui récupère le paquet de la zone active depuis /localizations/... le montage et à chaque changement de localisation. Enveloppez le fournisseur avec un <LocaleProvider> état de changement de localisation explicite (détecté automatiquement par le chemin de l’URL, ASP.NET le cookie de culture, ou navigator.language par défaut). Les chaînes que l’utilisateur voit pendant la brève fenêtre avant que le bundle ne se résolve soient rendues comme leurs clés brutes ; Le fournisseur échange les vraies cordes dès que la récupération atterrit.
// App.tsx — bootstrap minimal. PowerPortalsProProvider montage automatique
// DefaultLocalizerProvider donc useT() fonctionne dans tous les descendants.
import { LocaleProvider, PowerPortalsProProvider } from '@powerportalspro/react';
export function App() {
return (
<LocaleProvider>
<PowerPortalsProProvider>
<Routes />
</PowerPortalsProProvider>
</LocaleProvider>
);
}
Blazor
Enregistrez les répertoires de localisation dans les Program.cs serveurs via AddLocalizationDirectory. Le framework les lit au démarrage, et ils soutiennent tout IStringLocalizer / IStringLocalizer<T> résolu via DI.
// Program.cs
builder.Services.AddPowerPortalsPro(options =>
{
options.AddLocalizationDirectory("localization");
options.AddLocalizationDirectory("_content/MyApp.Client/localization");
});
Fichiers de localisation HTML
Pour des contenus plus longs comme les modèles d’emails, vous pouvez utiliser des fichiers HTML autonomes au lieu d’intégrer des chaînes HTML en JSON. Le nom du fichier encode le chemin complet de la clé de localisation et la culture, en utilisant le format {key-path}.{culture}.html.
Chaque segment séparé par points du nom du fichier correspond à un niveau de la hiérarchie des clés de localisation. L’avant-dernier segment est le code de culture (par enexemple , fr). Placez ces fichiers dans les mêmes annuaires de localisation enregistrés avec AddLocalizationDirectory.
Par exemple, la structure de fichiers suivante :
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
Le fichier emails.signup-confirmation.body.en.html correspond à la clé emails.signup-confirmation.body de localisation de la en culture. C’est équivalent à avoir le contenu HTML comme une valeur de chaîne dans votre fichier JSON à ce chemin de clé.
// Récupérer le contenu HTML à l’aide de la clé de localisation
var emailBody = _localizer["emails.signup-confirmation.body"];
Récupérez le contenu en utilisant la même IStringLocalizer clé correspondant au nom du fichier. Le contenu HTML est retourné sous forme de chaîne localisée et peut être rendu avec ToMarkupString().
Tableaux et étiquettes de colonnes
Les noms d’affichage des tableaux et des colonnes, descriptions et étiquettes de vues sont automatiquement résolus à partir des fichiers de localisation en utilisant la convention tables.{tableName}.label, tables.{tableName}.columns.{columnName}.label, et 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"
}
}
}
}
}
Note
Par défaut, le framework charge les métadonnées de chaque table dans votre environnement Dataverse. Pour tout portail qui prend en charge plusieurs langues, vous devez définir
LocalizeAllAvailableTables = falseet enregistrer uniquement les tables que vous utilisez réellement —AddTableToLocalizecharger chaque table ralentit le réchauffement de démarrage et remplit le cache de chaînes avec des étiquettes que vous n’affichez jamais (un coût payé par langue installée). Enregistrez chaque table dont les labels apparaissent dans l’interface (grilles, formulaires, sous-grilles, graphiques), car une table affichée qui n’est pas localisée affiche ses clés brutes au lieu des labels.account,contact, etadx_externalidentitysont inclus par défaut.
Voir les étiquettes
Les notices de la vue dans le menu déroulant du sélecteur de la vue grille sont localisées sous tables.{tableName}.views.{viewId}.label, où {viewId} est l’GUID de la vue enregistrée Dataverse (sans crochets, minuscules). Cela s’applique à la fois MainGrid aux sélecteurs de vues et SubGrid de vue.
// 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"
}
}
}
}
}
Note
Pour les vues personnalisées définies via
CustomViewDefinitions, la même convention s’applique — utiliser le GUID de la vue personnalisée comme clé. Si aucune étiquette localisée n’est trouvée, le nom de la vue provenant desGridViewDefinitionmétadonnées ou Dataverse est utilisé comme solution de secours.
Affichage des en-têtes de colonne
Les en-têtes de colonnes affichés dans des grilles sont résolus à l’aide d’un motif de secours. Le système cherche d’abord une étiquette de colonne spécifique à la vue en tables.{tableName}.views.{viewId}.columns.{columnName}.label. Si elle n’est pas trouvée, elle revient à l’étiquette de colonne au niveau de la table en tables.{tableName}.columns.{columnName}.label. Les infobulles suivent le même motif en utilisant .description au lieu de .label.
Cela vous permet de remplacer l’en-tête d’une colonne pour une vue spécifique sans affecter son label dans d’autres vues ou éditeurs.
// Remplacer un en-tête de colonne pour une vue spécifique
{
"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"
}
}
}
}
}
}
}
Note
Pour les colonnes provenant d’entités liées (par exemple
contact.emailaddress1), le nom de la colonne dans la clé utilise le format avec préfixe alias. Si aucune étiquette spécifique à une vue n’est trouvée, le système construit une étiquette à partir du nom de la colonne liée et de l’étiquette de la colonne parente (par exemple « Email (Primary Contact) »).
Étiquettes de choix (ensemble d’options)
Les inscriptions d’options de colonne de choix sont localisées différemment selon que l’ensemble d’options est à portée de tableau ou global.
Choix à portée de tableau
Les choix à portée de table sont localisés sous tables.{tableName}.choices.{choiceLogicalName}.values.{value}.label. Le nom logique choisi est précédé du nom de la table (par account_accountcategorycodeexemple ).
// En tables.en.json — choix à portée de tableau
{
"tables": {
"account": {
"choices": {
"account_accountcategorycode": {
"label": "Category",
"values": {
"1": { "label": "Preferred Customer" },
"2": { "label": "Standard" }
}
}
}
}
}
}
Choix mondiaux
Les choix globaux (ensembles d’options partagés entre plusieurs tables) sont localisés au niveau racine sous choices.{choiceLogicalName}.values.{value}.label, en dehors de toute section de table.
// En tables.en.json — choix globaux (au niveau racine, en dehors des « tables »)
{
"choices": {
"powerpagelanguages": {
"label": "Preferred Language",
"values": {
"1033": { "label": "English" },
"1036": { "label": "French" },
"1031": { "label": "German" }
}
}
}
}
Note
Les
ChoiceEditcomposants etMultiSelectChoiceEditrésolvent automatiquement les étiquettes de choix à partir du bon emplacement en fonction de laIsGlobalpropriété des métadonnées de la colonne.
Injection du localisateur
Il existe deux façons d’injecter le localisateur de chaînes :
IStringLocalizer— Accéder à toutes les clés de localisation globalement. Utilisez-le pour les étiquettes de tables, les chaînes partagées, ou lorsque vous avez besoin de laGetPrefixedLocalizerméthode.IStringLocalizer<T>— Portée portée à un type de composant spécifique. Les clés sont résolues par rapport au chemin de l’espace de noms du composant dans le fichier JSON (parcomponents.{Namespace}.{ComponentName}.{key}exemple ).
// Hook global de localisateur — récupère le localisateur actif et renvoie son
// 't' fonctionnait directement. Identité stable par rendu ; En effet, des déps sûrs.
import { useT, usePrefixedT } from '@powerportalspro/react';
function MyComponent() {
const t = useT();
const label = t('app.navigation.home');
// Les recherches à portée composante dans React sont du sucre sur un préfixe fixe. Le
// Le faisceau contient toujours des « composantes ». <FullTypeName>. <key>« forme —</key></FullTypeName>
// Passez cet espace de noms complet à usePrefixedT et appelez uniquement avec la clé.
const componentT = usePrefixedT(
'components.MyApp.Pages.MyComponent',
);
const title = componentT('title'); // → composants. MyApp.Pages.MyComponent.title
return <h1>{title}</h1>;
}// Localisateur global — accéder à n’importe quelle clé
[Inject]
private IStringLocalizer _localizer { get; set; } = null!;
// Localisateur à portée composante — les clés se résolvent par rapport à l’espace de noms du composant
[Inject]
private IStringLocalizer<MyComponent> _localizer { get; set; } = null!;Clés à portée composante
Lors de l’utilisation IStringLocalizer<T>de , les clés sont résolues en fonction de l’espace de noms et du nom de classe du composant. Par exemple, un composant en Pages.Editors.TextEdit.TextEditDemoPage résout des clés issues du components.{AssemblyName}.Pages.Editors.TextEdit.TextEditDemoPage.{key} JSON.
// En app.en.json — clés pour un composant sur Pages/Éditeurs/TextEdit/TextEditDemoPage
{
"components": {
"MyApp.Client": {
"Pages": {
"Editors": {
"TextEdit": {
"TextEditDemoPage": {
"title": "TextEdit",
"description": "A single-line text input."
}
}
}
}
}
}
}
// usePrefixedT épingle l’espace de noms complet UNE FOIS, donc les sites d’appel ne répètent que
// la touche de suivi. Résolutions contre
// 'composantes. MyApp.Client.Pages.Editors.TextEdit.TextEditDemoPage.title'
// dans le faisceau.
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') }} />
</>
);
}<!-- Dans la composante -->
@inject IStringLocalizer<TextEditDemoPage> _localizer
<h1>@_localizer["title"]</h1>
<p>@_localizer["description"].ToMarkupString()</p>Localisateur préfixé
Utilisez GetPrefixedLocalizer pour créer un sous-localisateur qui prépose automatiquement un préfixe à toutes les recherches clés. Cela est utile pour éviter les préfixes répétitifs dans les composants qui utilisent de nombreuses touches de la même section.
// usePrefixedT(préfixe) renvoie une fonction t avec chaque touche auto-prépendée
// avec '${préfixe}.'. Mémoïsé par valeur de préfixe pour que ce soit stable sur tous les deux
// rends (déps en effet sûrs).
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 /* ... */;
}// Sans préfixe — répétitif
var home = _localizer["app.navigation.home"];
var contacts = _localizer["app.navigation.contacts"];
// Avec préfixe — plus propre
var navLocalizer = _localizer.GetPrefixedLocalizer("app.navigation");
var home = navLocalizer["home"];
var contacts = navLocalizer["contacts"];HTML en chaînes localisées
Les chaînes localisées peuvent contenir du balisage HTML. Utilisez la méthode d’extension ToMarkupString() pour les rendre comme MarkupString dans les modèles Razor.
// dangerouslySetInnerHTML rend la chaîne localisée en HTML — le
// La valeur est un balisage de confiance puisque vous l’avez rédigé dans le bundle.
<p dangerouslySetInnerHTML={{ __html: t('description') }} /><!-- Affiche le balisage HTML à partir d’une chaîne localisée -->
<p>@_localizer["description"].ToMarkupString()</p>Trouver le meilleur partenaire
Utilisez FindLocalizedString la recherche de la première clé correspondante dans une liste de candidats. C’est utile pour les schémas de secours où vous voulez d’abord essayer une clé spécifique, puis revenir à une plus générale.
// 't()' renvoie la clé brute lorsqu’une chaîne manque, donc 't(clé) !== clé'
// Le test « Est-ce que cette clé a résolu ? » est sur lequel tu construis une marche de secours.
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`,
);// Essayez d’abord une clé spécifique, puis revenez à la clé générale
var label = _localizer.FindLocalizedString(
$"tables.{tableName}.columns.{columnName}.label",
$"tables.{tableName}.label");Du côté Réact, il useT() retourne la clé brute quand une chaîne manque — t(key) !== key c’est le test « est-ce que cette clé a été résolue ? » sur lequel vous construisez une marche de secours ? Enveloppez le patron dans un assistant quand vous en avez besoin sur plusieurs sites d’appel.
Interpolation d’arguments
Les cordes peuvent inclure des cordes {0}positionnelles , {1}, ... des espaces réservés qui sont remplacés lors de la recherche. Blazor transmet args comme un tableau params à IStringLocalizer; Réaction les fait passer comme second argument à t(). Les spécificateurs de format optionnels à l’intérieur d’un espace réservé ({0:N0}, {1:yyyy-MM-dd}) sont respectés uniquement par le pipeline de String.Format Blazor ; pour React, préformatez avec Intl.NumberFormat / Intl.DateTimeFormat avant de passer la valeur dedans.
// En app.en.json
{
"app": {
"welcome": "Welcome back, {0}! You have {1} unread messages."
}
}
// Args passait comme second argument à t() — positionnel, indexé par « {0} ».
const greeting = t('app.welcome', [userName, unreadCount]);// Args transmis via un tableau de params — l’indexeur d’IStringLocalizer les accepte.
var greeting = _localizer["app.welcome", userName, unreadCount];Chargement impatient (React)
En cas d’échec de cache, la pile React récupère paresseusement le bundle par ressource propriétaire (un lot débouncé par ~75 ms) et re-affiche le composant consommant lorsque les chaînes atteignent. Pour éliminer le bref flash des clés brutes, déclarez les préfixes dont une page aura besoin en premier lieu via useLocalization([...]), ou attendez le rendu jusqu’à ce qu’ils se résolvent via l’enveloppe de niveau supérieur <LocalizationBoundary> . Voir la page LocalizationBoundary pour le motif complet.
// Chargez à l’avance les bundles par ressource d’une page pour éviter un
// Un flash de touches brutes en naviguant vers l’intérieur. Des jetons qui sont déjà
// récupérés (ou en vol) sont silencieusement déduppés, donc appel depuis deux
// Les composants sur la même page produisent un total aller-retour.
import { useLocalization } from '@powerportalspro/react';
function AccountFormPage() {
useLocalization(['tables.account', 'tables.contact']);
return /* ... forme... */;
}
IStringLocalizer Interface
Propriétés
Nom | Type | Par défaut | Description |
|---|---|---|---|
Item | LocalizedString |
ItemMéthodes
Nom | Paramètres | Type | Description |
|---|---|---|---|
FindLocalizedString | string[] keys | LocalizedString | Retourne la première correspondance valide basée sur les clés fournies. |
GetAllStrings | bool includeParentCultures | IEnumerable<LocalizedString> | |
GetPrefixedLocalizer | string prefix | IPrefixedStringLocalizer | Renvoie un nouveau Localization.IPrefixedStringLocalizer qui précède le préfixe donné à toutes les recherches clés. |
FindLocalizedStringGetAllStringsGetPrefixedLocalizerLocalization.IPrefixedStringLocalizer qui précède le préfixe donné à toutes les recherches clés.