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

XORveR.com の日記

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

VisualStudioでjsonの文字列リソースを作ってみた。その1

T4 wpf DotNet

まえがき

dotnetでの文字リソースは扱いが厄介なので、利用者が勝手に編集できるようにjsonファイルで提供を考えた。

問題点

しかし、リソースのキーは

public const string Product_Name = "Product_Name";

といった風にしておきたい。
理由は言うまでもなく文字列リソースとコードの連携のためです。

解決策

T4テキストテンプレートで、CSharpコードからjsonファイルを作ればいーじゃん。

実装

CSharpコード

MessageAttribute を設けて、文字リソースの内容を記入しておきます。
[Strings.cs]

using System;

namespace Common.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; } }
	}
	/// <summary>
	/// Strings.tt で json に変換するクラス
	/// </summary>
	public static class Strings {

		/// <summary>
		/// product name
		/// </summary>
		[Message("File Protector XORveR")]
		public const string Product_Name = "Product_Name";

		[Message("English", true)]
		public const string ResText = "ResText";

		[Message("Entry Files", true)]
		public const string ProcessTab_Header = "ProcessTab_Header";
		[Message("Completed Files")]
		public const string CompletedTag_Header = "CompletedTag_Header";
		[Message("Failed Files")]
		public const string FailedTab_Header = "FailedTab_Header";

・・・・

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

T4テキストテンプレート

ファイルのプロパティでのカスタムツールの設定には TextTemplatingFileGenerator を指定しておきます。
[String.en-US.tt]

<#@ 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 att = null;
	PropertyInfo Message = null;
	PropertyInfo IsSeparate = null;
	foreach (Type t in asm.DefinedTypes) {
		if (t.Name == "MessageAttribute") {
			att = t;
			Message = t.GetProperty("Message");
			IsSeparate = t.GetProperty("IsSeparate");
			break;
		}
	}

	Type type = null;
	foreach (Type t in asm.DefinedTypes) {
		if (t.Name == "Strings") {
			type = t;
			break;
		}
	}

	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ファイル

[Strings.en-US.json]

{
  "Product_Name": "File Protector XORveR",

  "ResText": "English",

  "ProcessTab_Header": "Entry Files",
  "CompletedTag_Header": "Completed Files",
  "FailedTab_Header": "Failed Files",

  "Password_Newface": "new password",

・・・・

  "Company_Name": "XORveR.com"
}

あとがき

jsonは作ったけど、それからどーする?という話は、また別の機会に。
では!