UiPathのカスタムアクティビティ開発で、リソースの外部化とローカライズを行う

さあ[前回](https://qiita.com/masatomix/items/f3c5a36755860dba63ca)までで、カスタムアクティビティの表示のカスタマイズができたので、今回はその表示データの外部ファイル化とローカライズについてです。

外部化とは表示している文字列を設定ファイルに外出しすることです。ローカライズとは、英語圏では英語表示、日本語圏では日本語表示するなど、アプリが起動しているロケール？(カルチャというらしい)ごとに表示言語を切り替えることです。

表示するデータは外部ファイル(リソースファイル)にしておいて、リソースファイルをカルチャ毎に用意しておきます。アプリは起動された環境のカルチャに応じて、読み込むリソースファイルを切り替える、という仕組みですね。



さて、この外部化とローカライズのやりかたは、Visual Studioというかnupkgにはどうも標準的なやり方があるようで、それらを参考にします。

- [C#でアプリを多言語対応した際に行ったことまとめ](https://qiita.com/YSRKEN/items/a96bcec8dfb0a8340a5f)
- [アプリケーションのグローバライズとローカライズ](https://msdn.microsoft.com/ja-jp/library/1021kkz0.aspx)
- [ローカライズされた NuGet パッケージを作成する](https://docs.microsoft.com/ja-jp/nuget/create-packages/creating-localized-packages)


また、UiPath Studio のローカライズ(言語切り替え)についてですが、[バージョン2018.3から対応している](https://qiita.com/masatomix/items/5e56c31d1ef3b400da6f)ようなので、今回は稼働確認に UiPath Studio 2018.3.2を使用しています。


というわけで、今回のローカライズの話は、

- 表示している文字データの外部ファイル化
- 外部ファイルをカルチャ毎に作成
- 言語対応してビルドしたnupkgを UiPath Studio 2018.3.2 へインストール
- アプリ側(UiPath Studio)のカルチャを切り替えて、画面表示が変わっていることを確認する

というのがメインの話となります。


### その前にカルチャの確認
ちなみに UiPath Studio 2018.3の言語切り替えに応じて、いま使用しているカルチャが変化するのですが「いまどのカルチャで起動しているか」を、こんな感じのアクティビティを作ってコンソール出力してみました。

```c#
namespace Utils.PathUtils
{
    public sealed class CurrentCultureCheck : CodeActivity
    {
        protected override void Execute(CodeActivityContext context)
        {
            Console.WriteLine(Thread.CurrentThread.CurrentCulture);
            Console.WriteLine(Thread.CurrentThread.CurrentUICulture);
        }
    }
}
```

実行結果は、日本語で起動しているときは

```
  <-空かnull
ja
```

英語で起動しているときは、

```
  <-空かnull
en
```

となっていました。ようするにUiPath Studioの設定画面の言語切り替えによって、カルチャが en とか jaになったりするって事みたいですね。
ja-JP とかそんな値が返ってくるのを期待したのですが、まあ、とにかくこうなっています。


一応このソースへのリンクはコチラ。全体のどこに置いたかとか知りたい方はどうぞ。
https://github.com/masatomix/UiPath_Path/blob/c1e1944dd3c9875f932d45eabaf3f725e18ab392/Utils/PathUtils/CurrentCultureCheck.cs


## やってみる
では、外部化とローカライズ開始です。

### ソースコード

まず対応前の初期状態は、こちら。
https://github.com/masatomix/UiPath_Path/releases/tag/localization_init
ココから開始します。該当のコミットをCheckoutしてもらえばそのまま開発を開始できます

で、修正した結果の今回の成果物はこちら。
https://github.com/masatomix/UiPath_Path/releases/tag/localization_finish
該当のコミットをCheckoutしてもらえば、そのまま今回の成果物を確認できます。

さき書いてしまうと、差分はこんなかんじになっています。
https://github.com/masatomix/UiPath_Path/compare/localization_init...localization_finish



### 文字列の外部化
まずは文字列の外部ファイル化から。以下、プロジェクトにリソースファイルを追加する手順です。

プロジェクトを開いたら、ソリューションエクスプローラのプロジェクト名「Utils」を選択して ALT + Enter(右クリック >> プロパティでも同じ)。いつものアプリケーションの設定画面が表示されるので、左メニュー部の「リソース」をクリックします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/53adf6b8-0faa-afb3-c879-a500c448e4c2.png)

リソースファイルはまだないと言われてますので、作成するにはココをクリック、とあるのでその通りに。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/d1d7adde-52f4-26ad-e804-d59f940f4fe8.png)

リソースファイルが作成されました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/91c03737-a7dd-e394-c918-53c2eb655d85.png)

ソリューションエクスプローラにも、

- Resources.resx
- Resources.Designer.cs

が追加されていると思います。


最後に、下記のリソースのアクセス修飾子を Internal から Publicに変更しておくのがよいようです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/152db32d-65cc-cc33-ade1-2c332df4047b.png)

コレをやると、ソースの差分を見ると Resources.Designer.cs 内の Resourcesクラスがinternal -> public に変わったり、*.csprojファイル内の Resources.resx が PublicResXFileCodeGenerator ってのになったりしてました。
どうも
 [Properties.ResourcesがXAMLから参照できない](http://tkatochin.hatenablog.com/entry/20080623/1214203789) 
 っていうページを見ると、XAMLから呼び出すときにはpublicじゃないとダメ、ってことかな。またこんどしらべよう。



### 外部化した文字列を記述
つづいてリソースファイルに情報を追記していきます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/ecab3829-7593-90d1-ce5e-c58bc525d446.png)

上記のように、key1,key2に対してそれぞれ Hello World1. Hello World2. ってデータを設定しました。
このように設定すると、アプリケーションからは

```c#
string value1 = Utils.Properties.Resources.key1;
string value2 = Utils.Properties.Resources.key2;
Console.WriteLine(value1);
Console.WriteLine(value2);
```

ってアクセスすることができるようになります。

確認のため[nupkgを作成して、UiPath Studioにインストール](https://qiita.com/masatomix/items/1f63513e80313a99faeb#nupkg%E4%BD%9C%E6%88%90)して、UiPath Studioのコンソールにセットした値が表示できることを確認しておきましょう。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/4fb6fe2b-b42c-3098-1e91-5c1d5164f784.png)
こんな感じになるはずです。

ここまでで、文字列の外部リソース化が出来ました。

### DesignerMetadata.cs で使用する
さて、このように「文字列のリソースファイルへの外部化」を [見た目をカスタマイズする際にでてきた DesignerMetadata.cs](https://qiita.com/masatomix/items/f3c5a36755860dba63ca#%E8%A6%8B%E3%81%9F%E7%9B%AE%E3%82%92%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8Bdesignermetadatacs-%E3%81%AE%E8%BF%BD%E5%8A%A0) に適用します。

```c#:DesignerMetadata.cs(変更前)
namespace Utils
{
    public class DesignerMetadata : IRegisterMetadata
    {
        public void Register()
        {
            AttributeTableBuilder builder = new AttributeTableBuilder();
            addCustomAttributes_PathUtils_categories(builder);
            MetadataStore.AddAttributeTable(builder.CreateTable());
        }

        private void addCustomAttributes_PathUtils_categories(AttributeTableBuilder builder)
        {
            // 1.Activitiesペイン上のツリー構造を構築する。
            {
                string PathUtils_categories = "ツリー1.ツリー2.Utilities";
                builder.AddCustomAttributes(typeof(Combine), new CategoryAttribute(PathUtils_categories));
            }

            // 2.Activitiesペイン上のアクティビティ名
            {
                builder.AddCustomAttributes(typeof(Combine), new DisplayNameAttribute("パス連結アクティビティ"));
            }

            // 3.Activitiesペイン上のアクティビティをポイントしたときに表示されるツールチップ文言
            {
                builder.AddCustomAttributes(typeof(Combine), new DescriptionAttribute("ツールチップに説明文を書こう"));
            }

            // 4.プロパティペイン内のプロパティの説明文言
            {
                builder.AddCustomAttributes(typeof(Combine), nameof(Combine.PathArray), new DescriptionAttribute("このプロパティの説明を書く"));
            }

            // 5.プロパティペインの、カテゴリ名
            {
                builder.AddCustomAttributes(typeof(Combine), nameof(Combine.PathArray), new CategoryAttribute("インプット"));
            }

            // 6.プロパティペイン内のプロパティの変数名
            {
                builder.AddCustomAttributes(typeof(Combine), nameof(Combine.PathArray), new DisplayNameAttribute("パス配列"));
            }
        }
    }
}
```

これらの文字列を、外部化していきましょう。まずは、リソースファイルはこんな感じに。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/bfa15623-1dba-173d-07ca-96aace36ff23.png)


| 名前      | 値|
|:-----------------|:------------------|
Category1_Combine|ツリー1.ツリー2.Utilities|
DisplayName2_Combine|パス連結アクティビティ|
Description3_Combine|ツールチップに説明文を書こう|
Description4_Combine_PathArray|このプロパティの説明を書く|
Category5_Combine_PathArray|インプット|
DisplayName6_Combine_PathArray|パス配列|


つづいて、上記の名前を使用するように、ソースを下記の通り変更します。


```c#:DesignerMetadata.cs(変更後)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.ComponentModel;
using System.Threading;
using System.Activities.Presentation.Metadata;
using Utils.PathUtils;
using Utils.Properties; // ココ

namespace Utils
{
    public class DesignerMetadata : IRegisterMetadata
    {
        public void Register()
        {
            AttributeTableBuilder builder = new AttributeTableBuilder();
            addCustomAttributes_PathUtils_categories(builder);
            MetadataStore.AddAttributeTable(builder.CreateTable());
        }

        private void addCustomAttributes_PathUtils_categories(AttributeTableBuilder builder)
        {
            // 1.Activitiesペイン上のツリー構造を構築する。
            {
                builder.AddCustomAttributes(typeof(Combine), new CategoryAttribute(Resources.Category1_Combine)); // ココ
            }

            // 2.Activitiesペイン上のアクティビティ名
            {
                builder.AddCustomAttributes(typeof(Combine), new DisplayNameAttribute(Resources.DisplayName2_Combine));// ココ
            }

            // 3.Activitiesペイン上のアクティビティをポイントしたときに表示されるツールチップ文言
            {
                builder.AddCustomAttributes(typeof(Combine), new DescriptionAttribute(Resources.Description3_Combine));// ココ
            }

            // 4.プロパティペイン内のプロパティの説明文言
            {
                builder.AddCustomAttributes(typeof(Combine), nameof(Combine.PathArray), new DescriptionAttribute(Resources.Description4_Combine_PathArray));// ココ
            }

            // 5.プロパティペインの、カテゴリ名
            {
                builder.AddCustomAttributes(typeof(Combine), nameof(Combine.PathArray), new CategoryAttribute(Resources.Category5_Combine_PathArray));// ココ
            }

            // 6.プロパティペイン内のプロパティの変数名
            {
                builder.AddCustomAttributes(typeof(Combine), nameof(Combine.PathArray), new DisplayNameAttribute(Resources.DisplayName6_Combine_PathArray));// ココ
            }
        }
    }
}

```


画面表示に使用する文字列が、外部化されました。再度、UiPath Studioに配置して確認してみてください。下記のように、外部化する前とおなじＵＩが得られたかと思います。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/3273db6c-7195-d4ac-05a4-eeb120282146.png)


### 日本語リソースを追加
さあ勢いでデフォルトのリソースファイルに日本語の文字列を外部化してしまいましたが、実際はデフォルトのリソースファイルには英語の情報を記述しておくのがよさそうです。

リソースファイルの構成は

- Resources.resx ← デフォルトのリソースファイル
- Resources.ja.resx ← カルチャが ja のときに使用されるリソースファイル

とファイル名とカルチャ名が対応するようになっています。なのでResources.ja.resx を作成して、そちらには日本語のリソースを記述し、もとの Resources.resx には英語のリソースを記述するようにこれから変更していきます。

Resources.ja.resx の追加は以下の通り。

ソリューションエクスプローラの「Properties」を右クリック >> 追加 >> 新しい項目 を選んで、
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/7034bd26-896f-35be-60ff-d395759c263a.png)


リソースファイルを選択、ファイル名を Resources.ja.resx にすればOK.
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/cda27769-1044-5987-bb02-1f90fb634101.png)

ソリューションエクスプローラにも表示されていればOKですね。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/8b94e60b-f100-b4d3-2083-349f21b12825.png)


さて、いまのところ

- Resources.resx いきおいで書いた日本語リソース
- Resources.ja.resx 空っぽ

になっちゃったので、Resources.resx の内容を、Resources.ja.resx へ転記し、Resources.resxには英語の情報を記載します。つまりこんな感じ。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/7fb0409f-5a83-7739-6b18-fc2f6e5d27cf.png)


Resources.resx

| 名前      | 値|
|:-----------------|:------------------|
Category1_Combine|tree1.tree2.Utilities|
DisplayName2_Combine|Path Combine Activity|
Description3_Combine|Write descriptions on tooltips|
Description4_Combine_PathArray|Write descriptions on tooltips|
Category5_Combine_PathArray|Input|
DisplayName6_Combine_PathArray|Path Array|


Resources.ja.resx

| 名前      | 値|
|:-----------------|:------------------|
Category1_Combine|ツリー1.ツリー2.Utilities|
DisplayName2_Combine|パス連結アクティビティ|
Description3_Combine|ツールチップに説明文を書こう|
Description4_Combine_PathArray|このプロパティの説明を書く|
Category5_Combine_PathArray|インプット|
DisplayName6_Combine_PathArray|パス配列|





### 日本語リソースをnupkgに追加する

さて、リソースファイルをデフォルトと日本語版を作ったので、またまたnupkgにして完了、でよさそうですが、実はVisual Studioでビルドをすると、いままで作成されてきた

```
[プロジェクト名]\bin\Release\kino.UiPath.Utils.Activities.dll
```
だけでなく、

```
[プロジェクト名]\bin\Release\ja\kino.UiPath.Utils.Activities.resources.dll
```

というファイルができています。このファイルはカルチャーが ja のリソースファイルが入っているDLLです。

参考:
[ローカライズされた NuGet パッケージを作成する](https://docs.microsoft.com/ja-jp/nuget/create-packages/creating-localized-packages)


そしてこのファイルですが、いつものnupkg作成コマンド:

```
nuget.exe pack Utils.csproj -Prop Configuration=Release
```

をやっても nupkgに取り込まれないみたいなんですね。↓ほら、入ってないんですよ。。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/3b417286-bc61-1389-c0c3-4e6f9b7de1c9.png)



リソース用のDLLを含ませるオプションとかあるのかな？って調べましたがよく分からず、結局 nupkgを作成するときにパッケージングを制御する *.nuspec ファイルに以下の設定を追加しました。


```xml:Utils.nuspec
<?xml version="1.0"?>
<package >
  <metadata>
    <id>$id$</id>
    <version>$version$</version>
    <title>$id$</title>
    <authors>masatomix</authors>
    <owners>masatomix</owners>
    <licenseUrl>https://www.apache.org/licenses/LICENSE-2.0</licenseUrl>
    <projectUrl>https://github.com/masatomix/UiPath_Path</projectUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>
When using UiPath, I often want to call methods of .NET,...
</description>
    <releaseNotes>
Available in English.
</releaseNotes>
    <copyright>Copyright 2018 masatomix</copyright>
  </metadata>
    <!-- 追加ココから -->
    <files>
      <file src="bin\Release\ja\*.dll" target="lib\net45\ja" />
    </files>
    <!-- 追加ココまで -->
</package>
```

まあベタに、、ja 配下のファイルを lib\net45\ja へコピー、、と。 前に、[nuspecに書いてあった、微妙なfilesタグに記述の削除](https://qiita.com/masatomix/items/d62683a158da6d35f24d#%E3%81%9F%E3%81%B6%E3%82%93%E3%82%B3%E3%83%AC%E3%81%A7ok%E3%81%AA%E6%A7%8B%E6%88%90) に成功したんですが、またfilesが復活してしまいました。。まあ仕方ないです。


何にせよ、この構成で再度nupkgのビルドを実行します。できあがったら、作成された nupkg の中身を見てみましょう。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/3ea70626-02b3-7062-3e77-6dd8cb464c40.png)

`\ja\kino.UiPath.Utils.Activities.resources.dll` が 追加されましたねー。


### 確認
さあ、nupkgを、UiPath Studio 2018.3 へインストールして、起動してみましょう。結果は、

英語モードだと、、下記の通りデフォルトのリソースファイルが使われています。。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/5631732a-5d66-38c8-c89e-0c61af39e367.png)


そして、日本語モードだと下記の通り、日本語リソースファイルが使われています。。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/6d4f710e-6148-e2f9-3a64-5ea329a6e1fc.png)


いい感じですね。ちなみに、カルチャー(表示言語)の切替は、UiPath Studioの Start >> Settings >> Language で可能です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/4021eac7-340d-2b3a-cce3-5d7eece79158.png)






以上で、UiPath Studioの言語設定に応じて、表示する言語を切り替えることが出来ました！


今回取り扱わなかったのですが、XAML 上の文言の外部リソース化・ローカライズもあるのですが、またそれは別記事にします！


**2018/11/07追記**
[カスタムアクティビティ開発で、リソースの外部化とローカライズを行う(XAML版)](https://qiita.com/masatomix/items/b747d1532529f9a2977d)
別記事を作成しました。
**2018/11/07追記 ここまで**




お疲れ様でしたー。


##関連リンク

- [UiPathのカスタムアクティビティ開発で、見た目のカスタマイズを行う](https://qiita.com/masatomix/items/f3c5a36755860dba63ca)
この記事の前回の記事。ビルドの仕方とか、はしょってあるところはそっちに記載があるので、お手すきであれば見てみてください。
- [UiPath Studioで使用するカスタムアクティビティの作成方法](https://qiita.com/masatomix/items/1f63513e80313a99faeb)この記事の初回記事。
- [C#でアプリを多言語対応した際に行ったことまとめ](https://qiita.com/YSRKEN/items/a96bcec8dfb0a8340a5f) XAML側のローカライズはほぼここに情報が。。
- [アプリケーションのグローバライズとローカライズ](https://msdn.microsoft.com/ja-jp/library/1021kkz0.aspx)
- [ローカライズされた NuGet パッケージを作成する](https://docs.microsoft.com/ja-jp/nuget/create-packages/creating-localized-packages)
- [UiPath/Community.Activities](https://github.com/UiPath/Community.Activities)　中のヒトのサンプル
- [今回の完成版のソース](https://github.com/masatomix/UiPath_Path/releases/tag/localization_finish)
- [本アクティビティのマスターブランチ](https://github.com/masatomix/UiPath_Path)
- [nugetのダウンロードリンク kino.UiPath.Utils.Activities](https://www.nuget.org/packages/kino.UiPath.Utils.Activities/)