局所化
PowerPortalsProは、すべてのユーザー向けテキストに対してJSONベースのローカライゼーションシステムを使用しています。これにはコンポーネントラベル、テーブル名や列名、ビュー名、検証メッセージ、アプリケーション固有の文字列が含まれます。Blazorスタックは IStringLocalizerで読み、Reactスタックは useT() フックを通して読みます。両方のスタックは同じJSON形状を消費します。 app.en.json に一度作成された文字列が両方のスタックを駆動し(Dataverseメタデータ由来の tables.* 文字列などの共有リソースファイルも自動的に両方に流れます)。
ローカリゼーションファイル
ローカライズはサーバープロジェクトの localization ディレクトリにJSONファイルが配置されることで行われます。ファイルは命名規則 name.{culture}.json に従います(例: app.en.json、 app.fr.json)。同じJSON形状が、Reactスタック(ランタイム時にローカライザープロバイダーが取得)とBlazorスタック(起動時に IStringLocalizerが読み取る)の両方を駆動します。
// app.en.json
{
"app": {
"navigation": {
"home": "Home",
"contacts": "Contacts"
}
}
}
反応してください
React側では、ローカライザーは<PowerPortalsProProvider>によって自動マウントされており、マウント時やロケーション変更のたびにアクティブなロケーションのバンドルを/localizations/...から取得する<DefaultLocalizerProvider>が含まれています。明示的なローカレ切り替え状態(URLパス、ASP.NET カルチャークッキー、またはデフォルトでnavigator.language)を知りたい場合は、プロバイダーを<LocaleProvider>でラップします。バンドルが解決される前の短いウィンドウ中にユーザーが見る文字列は、生のキーとしてレンダリングされます。プロバイダーはフェッチが着地するとすぐに本物のストリングを交換します。
// App.tsx — 最小限のブートストラップ。PowerPortalsProProvider 自動マウント
// DefaultLocalizerProviderなので、useT()はすべての子孫で動作します。
import { LocaleProvider, PowerPortalsProProvider } from '@powerportalspro/react';
export function App() {
return (
<LocaleProvider>
<PowerPortalsProProvider>
<Routes />
</PowerPortalsProProvider>
</LocaleProvider>
);
}
ブレイザー
サーバーの Program.cs にローカリゼーションディレクトリを AddLocalizationDirectoryで登録します。フレームワークは起動時にそれらを読み取り、解決された IStringLocalizer やDI IStringLocalizer<T> をバックアップします。
// Program.cs
builder.Services.AddPowerPortalsPro(options =>
{
options.AddLocalizationDirectory("localization");
options.AddLocalizationDirectory("_content/MyApp.Client/localization");
});
HTMLローカリゼーションファイル
メールテンプレートのような長文コンテンツでは、HTML文字列をJSONに埋め込む代わりにスタンドアロンのHTMLファイルを使うことができます。ファイル名は、 {key-path}.{culture}.html形式で完全なローカリゼーションキーパスと文化をエンコードします。
ファイル名の各ドット区切られたセグメントは、ローカライゼーションキー階層のレベルに対応します。最後から2番目のセグメントは文化コード(例: en、 fr)です。これらのファイルは AddLocalizationDirectoryに登録されている同じローカリゼーションディレクトリに入れてください。
例えば、以下のファイル構造があります:
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
このファイルemails.signup-confirmation.body.en.html、en文化のローカライゼーションキーemails.signup-confirmation.bodyにマッピングされます。これは、HTMLの内容がJSONファイルのキーパスにある文字列値として存在するのと同等です。
// ローカライゼーションキーを使ってHTMLコンテンツを取得します
var emailBody = _localizer["emails.signup-confirmation.body"];
ファイル名に対応する同じ IStringLocalizer キーを使って内容を取得します。HTMLコンテンツはローカライズされた文字列として返され、 ToMarkupString()でレンダリング可能です。
テーブルラベルおよびカラムラベル
テーブルおよびカラムの表示名、説明、ビューラベルは、 tables.{tableName}.label、 tables.{tableName}.columns.{columnName}.label、 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"
}
}
}
}
}
注記
デフォルトでは、フレームワークはDataverse環境 内のすべての テーブルのメタデータを読み込みます。複数言語をサポートするポータルでは、
LocalizeAllAvailableTables = falseを設定し、AddTableToLocalizeで実際に使うテーブルのみを登録すべきです。すべてのテーブルを読み込むと起動ウォームアップが遅くなり、表示されないラベルで文字列キャッシュがいっぱいになります(インストール言語ごとに支払われるコストです)。UIにラベルが表示されているすべてのテーブル(グリッド、フォーム、サブグリッド、チャート)を登録してください。ローカライズされていない表示テーブルはラベルの代わりに生のキーをレンダリングします。account、contact、adx_externalidentityはデフォルトで含まれています。
ラベルを見る
グリッドビューセレクターのドロップダウンにあるビューラベルは tables.{tableName}.views.{viewId}.labelの下にローカライズされており、 {viewId} はDataverseに保存されたビューのGUID(括弧なし、小文字)です。これは MainGrid ビューセレクターと SubGrid ビューセレクターの両方に当てはまります。
// イン・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"
}
}
}
}
}
注記
CustomViewDefinitionsで定義されたカスタムビューでも同じ慣例が適用されます — カスタムビューのGUIDをキーとして使用します。ローカライズされたラベルが見つからない場合は、GridViewDefinitionやDataverseメタデータのビュー名をフォールバックとして使います。
列ヘッダーを見る
グリッドに表示される列ヘッダーはフォールバックパターンで解決されます。システムはまずtables.{tableName}.views.{viewId}.columns.{columnName}.labelでビュー固有の列ラベルを探します。見つからない場合は、tables.{tableName}.columns.{columnName}.labelのテーブルレベルの列ラベルに戻されます。ツールチップも同じパターンで、.labelではなく.descriptionを使います。
これにより、特定のビューの列のヘッダーを上書きしても、他のビューや編集者でのラベルに影響を与えません。
// 特定のビューの列ヘッダーを上書きします
{
"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"
}
}
}
}
}
}
}
注記
リンクされたエンティティ(例:
contact.emailaddress1)からの列の場合、キー内の列名はエイリアスで先頭に付く形式が使われます。ビュー固有のラベルが見つからない場合は、リンクされた列名とその親列ラベル(例:「Email (Primary Contact)」)からラベルを作成します。
選択(オプションセット)ラベル
選択列のオプションラベルは、オプションセットがテーブルスコープかグローバルかによって異なるローカライズされます。
テーブルスコープ選択
テーブルスコープの選択肢は tables.{tableName}.choices.{choiceLogicalName}.values.{value}.labelのローカライズされています。選択論理名はテーブル名(例: account_accountcategorycode)で接頭辞を付けます。
// tables.en.jsonでは — テーブルスコープ選択
{
"tables": {
"account": {
"choices": {
"account_accountcategorycode": {
"label": "Category",
"values": {
"1": { "label": "Preferred Customer" },
"2": { "label": "Standard" }
}
}
}
}
}
}
グローバル・チョイス
グローバル選択(複数のテーブルで共有されるオプションセット)は、 choices.{choiceLogicalName}.values.{value}.labelのルートレベルで、テーブルセクションの外にローカライズされています。
// tables.en.jsonにおいて — グローバル選択(ルートレベル、「テーブル」の外)
{
"choices": {
"powerpagelanguages": {
"label": "Preferred Language",
"values": {
"1033": { "label": "English" },
"1036": { "label": "French" },
"1031": { "label": "German" }
}
}
}
}
注記
ChoiceEditおよびMultiSelectChoiceEditコンポーネントは、カラムメタデータのIsGlobal特性に基づいて、正しい場所から選択ラベルを自動的に解決します。
ローカライザーの注入
文字列ローカライザーを注入する方法は2つあります:
IStringLocalizer— すべてのローカライゼーションキーにグローバルにアクセスできる。テーブルラベルや共有文字列、GetPrefixedLocalizerメソッドが必要なときに使ってください。IStringLocalizer<T>— 特定のコンポーネントタイプにスコープが設定されています。キーはJSONファイル内のコンポーネントの名前空間パス(例:components.{Namespace}.{ComponentName}.{key})に基づいて相対的に解決されます。
// グローバルローカライザーフック — アクティブなローカライザーを取得し、
// 直接機能。レンダリングごとの安定した同一性;実質的に安全です。
import { useT, usePrefixedT } from '@powerportalspro/react';
function MyComponent() {
const t = useT();
const label = t('app.navigation.home');
// Reactのコンポーネントスコープ検索は、固定プレフィックスの上にシュガーを付けています。その
// バンドルは依然として「コンポーネント」を運んでいます。<FullTypeName>。<key>形は――</key></FullTypeName>
// そのフルネームスペースをPrefixedTに渡し、キーだけで呼び出します。
const componentT = usePrefixedT(
'components.MyApp.Pages.MyComponent',
);
const title = componentT('title'); // →コンポーネント。MyApp.Pages.MyComponent.title
return <h1>{title}</h1>;
}// グローバルローカライザー — 任意のキーにアクセス
[Inject]
private IStringLocalizer _localizer { get; set; } = null!;
// コンポーネントスコープのローカライザー — キーはコンポーネントの名前空間に対して相対的に解決されます
[Inject]
private IStringLocalizer<MyComponent> _localizer { get; set; } = null!;コンポーネントスコープキー
IStringLocalizer<T>を使用する場合、キーはコンポーネントの名前空間とクラス名に基づいて解決されます。例えば、Pages.Editors.TextEdit.TextEditDemoPageのコンポーネントはJSONのcomponents.{AssemblyName}.Pages.Editors.TextEdit.TextEditDemoPage.{key}キーを解決します。
// app.en.json — コンポーネントのキーはPages/Editors/TextEdit/TextEditDemoPageにあります
{
"components": {
"MyApp.Client": {
"Pages": {
"Editors": {
"TextEdit": {
"TextEditDemoPage": {
"title": "TextEdit",
"description": "A single-line text input."
}
}
}
}
}
}
}
// usePrefixedTは完全な名前空間を一度だけピン留めするため、呼び出しサイトは繰り返しに過ぎません
// トレーリングキー。反対決議
// 「コンポーネント」だ。MyApp.Client.Pages.Editors.TextEdit.TextEditDemoPage.title'
// 束の中に。
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') }} />
</>
);
}<!-- コンポーネントにおいて -->
@inject IStringLocalizer<TextEditDemoPage> _localizer
<h1>@_localizer["title"]</h1>
<p>@_localizer["description"].ToMarkupString()</p>プレフィックス・ローカライザー
GetPrefixedLocalizerを使って、すべてのキー検索に自動的にプレフィックスを付けるサブローカライザーを作成します。これは、同じセクションの多くのキーを使うコンポーネントで重複するキープレフィックスを避けるのに役立ちます。
// usePrefixedT(prefixed) は、すべてのキーが自動前置く t-関数を返します
// 「${接頭辞}」と付けられています。プレフィックス値ごとにメモ化して安定しています
// レンダリング(実質的に安全な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 /* ... */;
}// 接頭辞なしでは反復的
var home = _localizer["app.navigation.home"];
var contacts = _localizer["app.navigation.contacts"];
// 接頭辞付き — cleaner
var navLocalizer = _localizer.GetPrefixedLocalizer("app.navigation");
var home = navLocalizer["home"];
var contacts = navLocalizer["contacts"];ローカライズされた文字列におけるHTML
ローカライズド文字列にはHTMLマークアップを含めることができます。 ToMarkupString() 拡張メソッドを使ってRazorテンプレートで MarkupString としてレンダリングしてください。
// dangerouslySetInnerHTMLはローカライズされた文字列をHTMLとしてレンダリングします —
// 価値は、あなたが束で作成したので信頼できるマークアップです。
<p dangerouslySetInnerHTML={{ __html: t('description') }} /><!-- ローカライズされた文字列からHTMLマークアップをレンダリングします -->
<p>@_localizer["description"].ToMarkupString()</p>最適なマッチを見つける
FindLocalizedStringを使って候補リストから最初の一致キーを調べます。これは、まず特定のキーを試してからより一般的なキーに戻すフォールバックパターンに役立ちます。
// 文字列が欠落すると生キーを返すため、't(key) !== key'
// 「このキーは解決したか?」というチェックは、フォールバックウォークオンを作った場合です。
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`,
);// まずは特定のキーを試してみて、その後は一般キーに戻る
var label = _localizer.FindLocalizedString(
$"tables.{tableName}.columns.{columnName}.label",
$"tables.{tableName}.label");React側では useT() 、文字列が欠落すると生キーを返します。 t(key) !== key 「このキーは解決されましたか?」というチェックで、フォールバックウォークオンを構築します。複数の通話サイトで必要なときには、パターンをヘルパーで包んでおきましょう。
引数補間
弦には位置の {0}、 {1}、...ルックアップ時に置き換えられるプレースホルダー。Blazorはargをパラメータ配列として IStringLocalizerに渡します。Reactはそれらを t()に2つ目の引数として渡します。プレースホルダー内のオプションフォーマット指定子({0:N0}、 {1:yyyy-MM-dd})はBlazorの String.Format パイプラインでのみ尊重されます。Reactの場合は、値を渡す前に Intl.NumberFormat / Intl.DateTimeFormat で事前フォーマットします。
// app.en.json
{
"app": {
"welcome": "Welcome back, {0}! You have {1} unread messages."
}
}
// Argsはt()の第2項として渡され、位置変数は「{0}」でインデックス付けされました。
const greeting = t('app.welcome', [userName, unreadCount]);// パラメータ配列を通じて渡されたArgsは、IStringLocalizerのインデクサがそれを受け入れます。
var greeting = _localizer["app.welcome", userName, unreadCount];イーガーローディング(React)
キャッシュミス時には、Reactスタックがリソースごとに1回のデボウンスバッチ(約75msごとに1バッチ)を怠惰に取得し、文字列が着くと消費コンポーネントを再レンダリングします。生キーの一瞬のフラッシュを避けるために、ページに必要な接頭辞を useLocalization([...])で最初に宣言するか、上位の <LocalizationBoundary> ラッパーで解決されるまでレンダリングを控えましょう。完全なパターンについてはLocalizationBoundaryページをご覧ください。
// ページごとのリソースごとのバンドルを最初にイージアロードして、
// ナビゲート時に生キーの点滅。すでに存在するトークン
// フェッチド(または飛行中)は静かに重複を解除されるため、2つから呼び出します
// 同じページにあるコンポーネントは1回の往復合計を生み出します。
import { useLocalization } from '@powerportalspro/react';
function AccountFormPage() {
useLocalization(['tables.account', 'tables.contact']);
return /* ...形を... */;
}
IStringLocalizer Interface
性質
名称 | 種類 | デフォルト | 概要 |
|---|---|---|---|
Item | LocalizedString |
Item方法
名称 | パラメータ | 種類 | 概要 |
|---|---|---|---|
FindLocalizedString | string[] keys | LocalizedString | 提供されたキーに基づいて最初の有効なマッチを返します。 |
GetAllStrings | bool includeParentCultures | IEnumerable<LocalizedString> | |
GetPrefixedLocalizer | string prefix | IPrefixedStringLocalizer | すべてのキールックアップに与えられたプレフィックスの前置きとなる新しい Localization.IPrefixedStringLocalizer を返します。 |
FindLocalizedStringGetAllStringsGetPrefixedLocalizerLocalization.IPrefixedStringLocalizer を返します。