読者です 読者をやめる 読者になる 読者になる

XORveR.com の日記

XORveR.com の公式ブログです。

いらいらして標準でない文字列リソース管理をやった。全く後悔していない。

DotNet T4 Refrection wpf

まえがき

現在開発中のアプリは、標準の国際化手順はガン無視して、文字列リソースは json で提供します。
しかし、文字列リソースを識別する識別子、ぶっちゃけ Properties\Resources.Designer.cs の代わりになる機構が無ければ管理がそりゃもう面倒です。

本題

そこで、逆転の発想。
Properties\Resources.Designer.cs の代わりにしている Strings.cs を元に、ニュートラルカルチャ用の json を作ってしまいましょう。
というのが、今回のお題です。

Strings.cs

Strings.cs は以下のようになっています。
MessageAttribute クラス。これはフィールドに設定する属性で、これがキモとなります。
ニュートラル言語は英語で書いています。Google先生頼りです。

using System;

namespace XORveR.I18n {
    [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
    public class MessageAttribute : Attribute {
        private string message;
        private bool isseparate;
        public MessageAttribute(string message, bool isseparate = false) {
            this.message = message;
            this.isseparate = isseparate;
        }
        public string Message { get { return this.message; } }
        public bool IsSeparate { get { return this.isseparate; } }
    }

    public static class Strings {




        // FabricTransform use
        [Message("must transform whole block", true)]
        public const string Cryptography_MustTransformWholeBlock = "Cryptography_MustTransformWholeBlock";
        [Message("transform beyond end of buffer")]
        public const string Cryptography_TransformBeyondEndOfBuffer = "Cryptography_TransformBeyondEndOfBuffer";

        // company name
        [Message("XORveR.com", true)]
        public const string Company_Name = "Company_Name";
    }
}

json

Strings.cs から作成される json は以下の感じです。

{




  "Cryptography_MustTransformWholeBlock": "must transform whole block",
  "Cryptography_TransformBeyondEndOfBuffer": "transform beyond end of buffer",

  "Company_Name": "XORveR.com"
}

どーうやって変換するの?

勘の良い読者なら分かるかと思いますが、

  1. コード生成と T4 テキスト テンプレート
  2. CodeDomProvider による動的コンパイル
  3. リフレクション

の合せ技です。

Strings.en-US.tt

こいつが変換の主役です。
基本的には手動でツール実行しなければなりませんが、AutoT4 拡張機能 を入れてやれば、ビルドの度に自動で Strings.en-US.json を作成してくれます。

<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.CodeDom.Compiler" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".json" encoding="utf-8" #>
<# 
    var filename = this.Host.ResolvePath("Strings.cs");
    var list = new List<string>();

    CodeDomProvider cscp = CodeDomProvider.CreateProvider("CSharp");
    var source = File.ReadAllText(filename);
    var param = new CompilerParameters();
    param.GenerateInMemory = true;
    var cr = cscp.CompileAssemblyFromSource(param, source);
    Assembly asm = cr.CompiledAssembly;

    Type type = asm.GetType("XORveR.I18n.Strings");
    Type att = asm.GetType("XORveR.I18n.MessageAttribute");
    PropertyInfo Message = att.GetProperty("Message");
    PropertyInfo IsSeparate = att.GetProperty("IsSeparate");
    foreach (FieldInfo field in type.GetFields()) {
        // 本当は、プロパティとかの順番は無保証・・・
        var mesatt = field.GetCustomAttribute(att);
        var message = (string)Message.GetValue(mesatt);
        var isseparate = (bool)IsSeparate.GetValue(mesatt);
        if (isseparate) {
            list.Add("");
        }
        if (field.Name.Equals("Company_Name")) {
            list.Add("  \"" + field.Name + "\": \"" + message.Replace("\"", "\\\"") + "\"");
        } else {
            list.Add("  \"" + field.Name + "\": \"" + message.Replace("\"", "\\\"") + "\",");
        }
    }
#>
{
<#
    foreach (string line in list) {
#>
<#= line #>
<#
    }
#>
}

json による国際化については、日を改めて紹介する予定です。

では!