LoginSignup
0

More than 3 years have passed since last update.

PowerApps コンポーネントフレームワーク : ローカライゼーション API サンプル

Last updated at Posted at 2019-07-18

前回 までは field と dataset タイプのカスタムコントロールを開発初してきました。今後は他のサンプルを紹介しながら各種機能を見ていきます。まず今回は多言語対応のサンプルとして localization API component サンプルを解説します。

このサンプルは、ボタンをクリックすると数字が大きくなる Increment component を多言語したもので、完成イメージは以下の通りです。

RESX リソースファイル

PCF プロジェクトではリソースファイルとして resx を指定できます。resx の利用は参照場所によって異なります。

マニフェスト内

取得したい文字列に対するキー名を直接指定します。

display-name-key="TS_LocalizationAPI_Display_Key"
description-key="TS_LocalizationAPI_Desc_Key"

ts コード内

コンテキストが提供する resources オブジェクトから取得できます。
参照: Resouces

  • context.resources.getResource: リソースファイル自体を取得
  • context.resources.getString: resx からキーに対応する文字列を取得

プロジェクトの作成

今回も既に作成した SampleSolution に追加するように開発していきます。

1. 前回まで作ってきた PCFControls フォルダに移動して、フィールド用のコントロールを作成。

mkdir LocalizationControl
cd LocalizationControl

2. 以下のコマンドを実行してプロジェクトを作成。

  • 名前空間とプロジェクト名の指定
  • template 引数は field を指定
pac pcf init --namespace SampleNamespace --name TSLocalizationAPI --template field

3. npm パッケージをリストアして任意の IDE でフォルダを開く。ここでは Visual Studio Code を利用。

npm install
code .

コントロールマニフェストの編集とリソースの追加

ローカライゼーションのリソースは RESX として作成し、コントロールマニフェストより参照します。またマニフェスト内のコントロール名や説明をローカライズする場合は、RESX に指定したキーを使います。

1. ControlManifest.Input.xml を以下の様に編集。

  • 名前や説明をリソースファイルか取得するようキー名のみ設定
  • resources に resx を追加。
ControlManifest.Input.xml
<?xml version="1.0" encoding="utf-8" ?>
<manifest>
  <control namespace="SampleNamespace" constructor="TSLocalizationAPI" version="1.0.0" display-name-key="TS_LocalizationAPI_Display_Key" description-key="TS_LocalizationAPI_Desc_Key" control-type="standard">
    <type-group name="numbers">
      <type>Whole.None</type>
      <type>Currency</type>
      <type>FP</type>
      <type>Decimal</type>
    </type-group>
    <property name="value" display-name-key="value_Display_Key" description-key="value_Desc_Key" of-type-group="numbers" usage="bound" required="true" />
    <resources>
      <code path="index.ts" order="1" />
      <css path="css/TS_LocalizationAPI.css" order="1" />
      <resx path="strings/TSLocalizationAPI.1033.resx" version="1.0.0" />
      <resx path="strings/TSLocalizationAPI.1041.resx" version="1.0.0" />
    </resources>
  </control>
</manifest>

2. css フォルダと strings フォルダを作成。
image.png

3. css フォルダに TS_LocalizationAPI.css ファイルを追加。

TS_LocalizationAPI.css
.SampleNamespace\.TSLocalizationAPI button.LocalizationSample_Button_Style {
    text-decoration: none;
    display: inline-block;
    font-size: 14px;
    margin: 4px 6px;
    cursor: pointer;
    color: white;
    border-radius: 0px;
    background-color: rgb(59, 121, 183);
    border: none;
    padding: 5px;
    text-align: center;
}

.SampleNamespace\.TSLocalizationAPI button.LocalizationSample_Input_Error_Style {
    color: red;
}

4. strings フォルダに TSLocalizationAPI.1033.resx を追加。

  • マニフェストに指定したキーに対応する value/comment を追加
TSLocalizationAPI.1033.resx
<?xml version="1.0" encoding="utf-8"?>
<root>
    <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
        <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
        <xsd:element name="root" msdata:IsDataSet="true">
            <xsd:complexType>
                <xsd:choice maxOccurs="unbounded">
                    <xsd:element name="metadata">
                        <xsd:complexType>
                            <xsd:sequence>
                                <xsd:element name="value" type="xsd:string" minOccurs="0" />
                            </xsd:sequence>
                            <xsd:attribute name="name" use="required" type="xsd:string" />
                            <xsd:attribute name="type" type="xsd:string" />
                            <xsd:attribute name="mimetype" type="xsd:string" />
                            <xsd:attribute ref="xml:space" />
                        </xsd:complexType>
                    </xsd:element>
                    <xsd:element name="assembly">
                        <xsd:complexType>
                            <xsd:attribute name="alias" type="xsd:string" />
                            <xsd:attribute name="name" type="xsd:string" />
                        </xsd:complexType>
                    </xsd:element>
                    <xsd:element name="data">
                        <xsd:complexType>
                            <xsd:sequence>
                                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
                            </xsd:sequence>
                            <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
                            <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
                            <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
                            <xsd:attribute ref="xml:space" />
                        </xsd:complexType>
                    </xsd:element>
                    <xsd:element name="resheader">
                        <xsd:complexType>
                            <xsd:sequence>
                                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                            </xsd:sequence>
                            <xsd:attribute name="name" type="xsd:string" use="required" />
                        </xsd:complexType>
                    </xsd:element>
                </xsd:choice>
            </xsd:complexType>
        </xsd:element>
    </xsd:schema>
    <resheader name="resmimetype">
        <value>text/microsoft-resx</value>
    </resheader>
    <resheader name="version">
        <value>2.0</value>
    </resheader>
    <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
    </resheader>
    <resheader name="writer">
        <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
    </resheader>
    <data name="PCF_LocalizationSample_ButtonLabel" xml:space="preserve">
        <value>Increment</value>
        <comment>Label for TSLocalizationAPI's Button</comment>
    </data>
    <data name="TS_LocalizationAPI_Display_Key" xml:space="preserve">
        <value>Sample Localization Control</value>
        <comment>Localization Sample Localized Control Name</comment>
    </data>
    <data name="TS_LocalizationAPI_Desc_Key" xml:space="preserve">
        <value>This control showcases usage of localization.</value>
        <comment>Localization Sample Localized Control Description</comment>
    </data>
    <data name="value_Display_Key" xml:space="preserve">
        <value>Value</value>
        <comment>Localization Sample Control Main Property Localized Name</comment>
    </data>
    <data name="value_Desc_Key" xml:space="preserve">
        <value>Shows the field that the control is mapped to.</value>
        <comment>Localization Sample Control Main Property Localized Description</comment>
    </data>
</root>

5. 同様に TSLocalizationAPI.1041.resx を追加。

TSLocalizationAPI.1041.resx
<?xml version="1.0" encoding="utf-8"?>
<root>
    <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
        <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
        <xsd:element name="root" msdata:IsDataSet="true">
            <xsd:complexType>
                <xsd:choice maxOccurs="unbounded">
                    <xsd:element name="metadata">
                        <xsd:complexType>
                            <xsd:sequence>
                                <xsd:element name="value" type="xsd:string" minOccurs="0" />
                            </xsd:sequence>
                            <xsd:attribute name="name" use="required" type="xsd:string" />
                            <xsd:attribute name="type" type="xsd:string" />
                            <xsd:attribute name="mimetype" type="xsd:string" />
                            <xsd:attribute ref="xml:space" />
                        </xsd:complexType>
                    </xsd:element>
                    <xsd:element name="assembly">
                        <xsd:complexType>
                            <xsd:attribute name="alias" type="xsd:string" />
                            <xsd:attribute name="name" type="xsd:string" />
                        </xsd:complexType>
                    </xsd:element>
                    <xsd:element name="data">
                        <xsd:complexType>
                            <xsd:sequence>
                                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
                            </xsd:sequence>
                            <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
                            <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
                            <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
                            <xsd:attribute ref="xml:space" />
                        </xsd:complexType>
                    </xsd:element>
                    <xsd:element name="resheader">
                        <xsd:complexType>
                            <xsd:sequence>
                                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                            </xsd:sequence>
                            <xsd:attribute name="name" type="xsd:string" use="required" />
                        </xsd:complexType>
                    </xsd:element>
                </xsd:choice>
            </xsd:complexType>
        </xsd:element>
    </xsd:schema>
    <resheader name="resmimetype">
        <value>text/microsoft-resx</value>
    </resheader>
    <resheader name="version">
        <value>2.0</value>
    </resheader>
    <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
    </resheader>
    <resheader name="writer">
        <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
    </resheader>
    <data name="PCF_LocalizationSample_ButtonLabel" xml:space="preserve">
        <value>増加</value>
        <comment> TSLocalizationAPI ボタンのラベル</comment>
    </data>
    <data name="TS_LocalizationAPI_Display_Key" xml:space="preserve">
        <value>ローカライゼーションサンプルコントロール</value>
        <comment>ローカライゼーションサンプルコントロールの名前</comment>
    </data>
    <data name="TS_LocalizationAPI_Desc_Key" xml:space="preserve">
        <value>ローカライゼーションの使い方を示すサンプル</value>
        <comment>ローカライゼーションサンプルコントロールの説明</comment>
    </data>
    <data name="value_Display_Key" xml:space="preserve">
        <value>バインドされたフィールドの値</value>
        <comment>値用の文字列</comment>
    </data>
    <data name="value_Desc_Key" xml:space="preserve">
        <value>バインドされたフィールドの値が表示される</value>
        <comment>説明用の文字列</comment>
    </data>
</root>

6. ターミナルより以下コマンドでビルドを実行。

npm run build

index.ts の編集

1. まずは import の追加。

import { IInputs, IOutputs } from "./generated/ManifestTypes";

2. クラスとプロパティの定義。

export class TSLocalizationAPI implements ComponentFramework.StandardControl<IInputs, IOutputs> {
    // 現在の値を持つプロパティ 
    private _value: number;
    // PCF への変更通知コールバック
    private _notifyOutputChanged: () => void;
    // ラベル用
    private label: HTMLInputElement;
    // ボタン用
    private button: HTMLButtonElement;
    // カスタムコントロールを保持するコンテナ
    private _container: HTMLDivElement;
}

3. init 初期化メソッドを追加。

  • context.resources.getString() : リソースファイルからキーに対応する文字列の取得。
/**
 * init メソッド
 * @param context : 各種オブジェクトや API へのアクセスを提供するコンテキスト
 * @param notifyOutputChanged : 出力変更通知のコールバック
 * @param state : 前回保存したステート
 * @param container : UI コントロールを保持するコンテナ
 */
public init ( context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container: HTMLDivElement )
{
    // ラベルの作成と blur イベントの設定
    this.label = document.createElement( "input" );
    this.label.setAttribute( "type", "label" );
    this.label.addEventListener( "blur", this.onInputBlur.bind( this ) );
    // ボタンの作成。
    this.button = document.createElement( "button" );
    // リソースよりローカライズされた文字列の取得
    this.button.innerHTML = context.resources.getString( "PCF_LocalizationSample_ButtonLabel" );
    this.button.classList.add( "LocalizationSample_Button_Style" );
    this._notifyOutputChanged = notifyOutputChanged;
    // クリックイベントの設定
    this.button.addEventListener( "click", this.onButtonClick.bind( this ) );
    // コンテナの作成と要素の追加
    this._container = document.createElement( "div" );
    this._container.appendChild( this.label );
    this._container.appendChild( this.button );
    container.appendChild( this._container );
}

3. インプットとボタン用のコールバックメソッドを追加。

private onButtonClick ( event: Event ): void
{
    this._value = this._value + 1;
    this._notifyOutputChanged();
}

private onInputBlur ( event: Event ): void
{
    let inputNumber = Number( this.label.value );
    this._value = isNaN( inputNumber ) ? ( this.label.value as any ) as number : inputNumber;
    this._notifyOutputChanged();
}

4. PCF からの変更通知を処理する updateView メソッドを追加。

  • context.parameters.value: フィールドからの値を保持。raw, formatted, error 等
  • context.parameters.value.error でエラーを取得
public updateView ( context: ComponentFramework.Context<IInputs> ): void
{
    // 現在の値を取得してラベルの表示
    this._value = context.parameters.value.raw;
    this.label.value = this._value != null ? this._value.toString() : "";
    if ( context.parameters.value.error )
    {
        this.label.classList.add( "LocalizationSample_Input_Error_Style" );
    }
    else
    {
        this.label.classList.remove( "LocalizationSample_Input_Error_Style" );
    }
}

5. PCF 側に値を返す getOutputs メソッドを追加。

public getOutputs (): IOutputs
{
    // custom code goes here - remove the line below and return the correct output
    let result: IOutputs = {
        value: this._value
    };
    return result;
}

6. コントロール破棄時に呼ばれる destroy を追加。特に今回は処理なし。

public destroy (): void
{
}

7. npm run build でビルド。エラーがないことを確認。

コントロールのパッケージ化と配布

これまでと同じ方法でパッケージ化と配布を実行。

1. SampleSolution フォルダに移動し、以下のコマンドを実行。

pac solution add-reference --path ../LocalizationControl

2. SampleSolution\Other\Solution.xml でバージョン情報を更新。
image.png

3. 以下コマンドでパッケージをビルド。

msbuild /restore
msbuild /p:configuration=Release

4. https://make.powerapps.com に接続。管理者権限でログイン。すでに SampleSolution を入れた環境で Solutions を選択し、Import をクリック。
image.png

5. インポート時にパッケージが更新される旨が表示されるので、そのまま次へ。

6. 次の画面も既定のまま「インポート」をクリック。

7. インポートが終わったら任意の数値列にコントロールを設定。ユーザーの言語によって表示される内容変わることを確認。

[フォーム:日本語]
image.png
[フォーム:英語]
image.png

ソリューションの多言語サポート

PCF コントロールだけでなく、ソリューション自体も多言語化がサポートされます。
方法はシンプルで、Other\Solution.xml ファイルに直接書き込みます。

Solution.xml
<LocalizedNames>
  <!-- Localized Solution Name in language code -->
  <LocalizedName description="SampleSolution" languagecode="1033" />
  <LocalizedName description="サンプルソリューション" languagecode="1041" />
</LocalizedNames>

<LocalizedNames>
  <!-- Localized Cds Publisher Name in language code-->
  <LocalizedName description="SamplePublisher" languagecode="1033" />
  <LocalizedName description="サンプル発行元" languagecode="1041" />
</LocalizedNames>

<Descriptions>
  <!-- Description of Cds Publisher in language code -->
  <Description description="SamplePublisher" languagecode="1033" />
  <Description description="サンプル発行元" languagecode="1041" />
</Descriptions>

まとめ

resx は C# 開発に慣れている人であれば容易に使えますが、Visual Studio Code での操作が不便なため、Visual Studio で直接作成、編集したり RESX Manager 等のツールを利用したほうがいいかもしれません。

次回はステート管理の機能のサンプルである Control State API component を見ていきます。

目次へ戻る
次の記事へ

参考

RESX Manager

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0