XORveR.com の日記

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

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

まえがき

現在開発中のアプリは、標準の国際化手順はガン無視して、文字列リソースは 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 による国際化については、日を改めて紹介する予定です。

では!