Haxeで地域・言語別で異なるJavaScriptファイルを出力する。

いわゆる多言語対応ですね。

多言語対応は悩ましい問題ですが、Haxeを使えばJavaScript単体の場合とはまったく異なるアプローチで実現可能になります。

例えば、コンパイルオプションを1つ変更するだけで同じコードから別々のJavaScriptファイルが出力されるとしたら、とてもスムーズに多言語対応ができると思いませんか？

Haxeでは、
・```-D lang=ja```をつけてコンパイルすると日本語用のJavaScriptファイルが出力される
・```-D lang=en```をつけてコンパイルすると英語用のJavaScriptファイルが出力される
という仕組みを簡単に実現できます

それでは、その手順を紹介していきます。

#JavaScript多言語化の手順

##1. 言語別リソースを準備する

多言語化なので言語ごとのリソースファイルが必要になります。
あとで読み込みやすいように[Haxeの構造体の構文](http://haxe.org/manual/struct?lang=jp)で記述して保存しましょう。

日本語:

```hx:locale/ja.hx
{
	hello : "こんにちは",
	world : "世界",
}
```

英語:

```hx:locale/en.hx
{
	hello : "Hello",
	world : "world",
}
```

リソースファイルは、これでOK。これら2つのファイルをプロジェクトフォルダ直下のlocaleフォルダに置きましょう。

##2. リソースファイルをクラスに読み込むマクロを用意する

Haxeでは、@:buildメタデータを使うことでマクロでクラスのフィールドをコンパイル時に生成することが可能になります。

@:buildの使い方は[Building Types with Macros](http://haxe.org/manual/macros/build)に乗っています。

先ほどの、リソースファイルから情報をよみこんでクラスの変数として出力するには以下のように記述します。

```hx:src/LocalizeString.hx
#if macro
import haxe.format.JsonParser;
import haxe.macro.Compiler;
import haxe.macro.Expr;
import haxe.macro.Context;
import sys.io.File;
#end

#if !macro
@:build(LocalizeTools.build()) 
class LocalizedString {
	//ここにstatic publicな変数が自動生成される。
}
#end

class LocalizeTools {
	
	#if macro
	//リソースファイルを設定したディレクトリを指定。
	static private inline var DIR:String = "locale";
	
	//デフォルトの言語を指定
	static private inline var DEFAULT_LANG:String = "en";
	#end
	
	//フィールドを生成
    macro public static function build() : Array<Field> {
        var pos = Context.currentPos();
        var fields = Context.getBuildFields();
		
		//コンパイル時のフラグを取得
		var lang = Compiler.getDefine("lang");
		if (lang == null) {
			lang = DEFAULT_LANG;
			Compiler.define("lang", lang);
		}
		
		var data = null, string = null;
		var file = '$DIR/$lang.hx';
		
        try {
			//ファイルを読み込み
			var reader = File.read(file);
			string = reader.readAll().toString();
			reader.close();
		} catch ( d:Dynamic ) {
			Context.error('can\'t read $file : $d', pos);
			return fields;
		}
		
		var filePos = Context.makePosition({min : 0, max : string.length, file:file});
        try {
			//リソースを構文解析
			data = Context.parseInlineString(string, filePos);
		} catch ( d:Dynamic ) {
			Context.error( 'can\'t parse : $d', filePos );
			return fields;
		}
		
		switch (data.expr) {
			case EObjectDecl(array) :
				for (e in array) {
					//型推論
					var t = Context.typeExpr(e.expr);
					
					//取得した値をフィールドにする
					fields.push({ 
						name : e.field,
						doc : null, 
						meta : [], 
						access : [APublic, AStatic], 
						kind : FVar(Context.toComplexType(t.t), e.expr), 
						pos : pos 
					});
				}
			
			//構造体の構文じゃなければエラー
			default :
				Context.error( data.expr + " is not suppoted value", filePos);
		}
        
		return fields;
    }
}

/* WTFPL (Do What The Fuck You Want To Public License) */
```

srcフォルダにLocalizedString.hxというファイルをつくって、このコードを貼り付けましょう。

## LocalizedStringの変数を利用する

上の日本語リソースを使用した場合、LocalizeStringクラスは以下のように記述した場合と同じものが生成されます。

```hx
class LocalizedString {
	public static var hello:String = "こんにちは";
	public static var world:String = "世界";
}
```

言語別のリソースファイルを使用する場合は、以下のように普通にLocalizedStringクラスの変数を使用するだけです。

```hx:src/Main.hx
class Main {
	static function main() {
		trace(LocalizedString.hello + ", " + LocalizedString.world);
	}
}
```

## コンパイルする

これまで出てきたファイルは以下のような構造で配置されています。

```
project
├ locale
│ ├ en.hx
│ └ ja.hx
└ src
  ├ LocalizedString.hx
  └ Main.hx
```

### 日本語
それでは、projectフォルダ下で以下のコマンドをコンパイルしてみましょう。

```
haxe -js ja.js -cp src -main Main -D lang=ja
```

これで日本語用のJavaScriptが生成されました。

```js:ja.js
(function () { "use strict";
var LocalizedString = function() { };
var LocalizeTools = function() { };
var Main = function() { };
Main.main = function() {
	console.log(LocalizedString.hello + ", " + LocalizedString.world);
};
LocalizedString.hello = "こんにちは";
LocalizedString.world = "世界";
Main.main();
})();
```

ちゃんと日本語リソースが埋め込まれていることがわかります。

そして実行するとコンソールに以下のテキストが表示されます。

```
こんにちは, 世界
```

### 英語

英語リソースの方も見てみます。

```
haxe -js en.js -cp src -main Main -D lang=en
```

これで、コンソールに出力されるテキストが以下のように変わりました

```
Hello, world
```

これで、JavaScriptの多言語化がうまくいきましたね。

# Haxeで多言語対応のTipsあれこれ

Haxeでの多言語対応を行う上でのTipsをいくつか紹介していきます

## 言語別の処理を記述する

コンパイル時に指定したフラグは、言語ごとに処理を分岐させるためにも利用できます

```hx
#if (lang == en)
	//英語圏での処理
#else
	//非英語圏での処理
#end
```

詳細は、
https://github.com/HaxeFoundation/HaxeManual/blob/master/md/manual/lf-condition-compilation.md
を見てください。

## 文字列以外をリソースにする

リソースファイルには、以下のように文字列以外も使用することができます。

```hx
{
	num : 1,
	obj : {
		arr : [1.1, 1.2, 1.3],
		flag : true,
	}
}
```

これらは型推論がされるので、リソースの利用法に間違いがあればコンパイル時に検出することができます

## JSONフォーマットを使う

haxeの構造体の構文は、JSON記法をサポートしています。他の言語でも同じリソースファイルを使う場合はJSON形式で記述しましょう。

## JS以外の出力の多言語化
ここで紹介した多言語化の方法は、JavaScript出力に限定されるものではありません。他の言語への出力でも同じ方法で多言語化ができます。

## デッドコード削除
コンパイルオプションの```-D dce=full```を指定することで、リソースの持つ変数のうち実際に使用されているものだけが、JavaScriptに出力されます。

## 入力補完
驚くべきことに、リソースの利用時にはちゃんと入力補完がされます。

FlashDevelopの場合は以下のようになります。

![flashDev.png](https://qiita-image-store.s3.amazonaws.com/0/2685/687a15c9-94f9-d83e-0da7-d19260cc043f.png)

IDEで入力補完を表示するために、特別な方法はいりません。

マクロで自動生成された変数が、正しく入力補完される理由は簡単です。Haxeの入力補完はコンパイラが提供する機能で、入力補完時もマクロが実行されるので生成された変数も補完対象に含まれるということです。