delete from hateblo.jp where 1=1;

タイトルに意味はありません。

transifexで多言語化する場合のめんどくささを改善する方法

対象

  • transifexで多言語化をしている人
  • リソースファイル数が多く、苦労している人
  • Windows環境であること

対応等

わけがあって、tx.exeを配布できないため、txファイルを取得して、所定の箇所に配布するという仕組みを作り上げた。
手順としては以下のとおり。

  1. transifexからtxクライアントをダウンロード(MSXML2.XMLHTTPからADODB.Stream経由でファイルを吸い出す)
  2. txクライアントの設定を行う(cmd経由でのバッチ処理)
  3. txクライアントで翻訳ファイル取得を行う(cmd経由でのバッチ処理)
  4. 所定の箇所へ配置する(FileSystemObjectでの処理)
PowerShell→断念

これらを手っ取り早く実装しようと思った場合、Windows PowerShellか...と思い、実装したところ、
署名等を行っていない場合、実行されない事実が判明。
集合の扱い等、取り回しがなれないので断念

WSH→できそう→できた

検索すると、adodb - Windows Script Host (jscript): how do i download a binary file? - Stack Overflowという質問があり、WSHでファイルをダウンロードしているコードを発見。
適当に関数に切り出し(後述のdownloadFile関数を参照)。

本体プログラム

  1. fetch_translations.jsとか名前をつけてプロジェクト直下に配置
  2. そのjsを実行する
  3. 黒い画面が表示されるので、必要に応じて情報を入力する。
  4. DONE!が表示されたら、処理完了。

なお、以下のプログラムはいかなる状態であっても無保証です。責任取りません。
stackoverflowで回答のあったdownloadFile関数以外についてはWTFPLライセンスとします。

//各種設定を行う(プロジェクト名は必ず設定すること!)
var settingTransifexProjectUrl = "https://www.transifex.com/projects/p/プロジェクト名";
//txクライアントのダウンロード用URL
var settingTransifexClient = "http://files.transifex.com/transifex-client/0.10/tx.exe";
//transifexのホストURL(基本的に変わることはない認識)
var settingTransifexHost = "https://www.transifex.com";

//現在のディレクトリを取得する
var objWS = WScript.CreateObject("WScript.Shell");
var pathRoot = objWS.CurrentDirectory;
d("CurrentDirectory",objWS.CurrentDirectory);

//作業用ディレクトリ(transifex)を作成する
var objFSO = WScript.CreateObject('Scripting.FileSystemObject');
var pathWork = objFSO.BuildPath(pathRoot,"transifex");

if(!objFSO.FolderExists(pathWork)){
	d("CreateDirectory",pathWork);
	objFSO.CreateFolder(pathWork);
}

//txクライアントをダウンロードする(transifex/tx.exe)
var pathTxClient = objFSO.BuildPath(pathWork,"tx.exe");
if(!objFSO.FileExists(pathTxClient)){
	d("Download",settingTransifexClient);
	if(!downloadFile(objFSO, settingTransifexClient ,pathTxClient)){
		e("Cannot download client. Abort",settingTransifexClient);
	}
	d("Download done",pathTxClient);
}

//transifexをセットアップするバッチファイルを作成する(transifex/fetch.bat)
var pathBatch = objFSO.BuildPath(pathWork,"fetch.bat");
if (objFSO.FileExists(pathBatch)){
	d("Delete file",pathBatch);
	objFSO.DeleteFile(pathBatch);
}


//.tx フォルダがある場合は一部処理を省略する
var pathTxConfig = objFSO.BuildPath(pathWork,".tx");
var isSkipInit = "";
if(objFSO.FolderExists(pathTxConfig)){
	isSkipInit = "rem ";
}

var objTextFile = objFSO.CreateTextFile(pathBatch, true);
objTextFile.WriteLine("@echo off");
objTextFile.WriteLine("cd " + pathWork);
objTextFile.WriteLine(isSkipInit + "echo transifex initalize...");
objTextFile.WriteLine(isSkipInit + "tx init --host=" + settingTransifexHost);
objTextFile.WriteLine(isSkipInit + "echo.");
objTextFile.WriteLine(isSkipInit + "echo.");
objTextFile.WriteLine("echo setup transifex: " + settingTransifexProjectUrl);
objTextFile.WriteLine("tx set --auto-remote " + settingTransifexProjectUrl);
objTextFile.WriteLine("echo.");
objTextFile.WriteLine("echo.");
objTextFile.WriteLine("echo fetch data...");
objTextFile.WriteLine("tx pull  -a -s");
objTextFile.WriteLine("echo done. Press ANY key to continue");
objTextFile.WriteLine("pause");
objTextFile.WriteLine("");
objTextFile.Close();

//ファイルを取得する前に取得するフォルダがない状態にする(transifex/translationsを削除)
var pathTranslation = objFSO.BuildPath(pathWork,"translations");
if(objFSO.FolderExists(pathTranslation)){
	d("Delete folder force",pathTranslation);
	objFSO.DeleteFolder(pathTranslation,true);
}

//バッチファイルを実行する(transifex/fetch.bat)
d("run batch",pathBatch);
objWS.Run("cmd /C " + pathBatch,1,true);
d("done batch",pathBatch);

//結果を確認し、フォルダができていない(取得できていない)場合終了する
if(!objFSO.FolderExists(pathTranslation)){
	e("Folder is not exists. Failed to fetch data",pathTranslation);
}

//取得したファイルを配布する
//最初は以下の処理を組み込まずに実行して、transifex/translationsに何ができるのかを確認したほうがよい。
//確認したら、次のように配置するものを設定する。(!!必ず設定すること!!)
//例: 
// map(objFSO,pathTranslation+"/androidproject.strings",pathRoot+"/AndroidProject/src/main/res/values-XX","strings.xml");
// 上記設定の場合、以下のとおりファイルを配置します。(フォルダがない場合は自動的に作成します)
// XXとなっている箇所をそれぞれのロケール(en/ja)に置き換えます。(ファイル名の箇所にXXがあっても同様。大文字小文字は区別されます)
// デフォルトでenとしているので、変更する必要がある場合はmap関数のところでreplaceしているのでその箇所を修正してください。
// transifex/translations/androidproject.strings/en.xml → AndroidProject/src/main/res/values/string.xml
// transifex/translations/androidproject.strings/ja.xml → AndroidProject/src/main/res/values-ja/string.xml
// 

WScript.Echo("DONE!");

//処理終了
////////////////////////////////////////////////

/* Download file
 * inherit from http://stackoverflow.com/questions/4164400/windows-script-host-jscript-how-do-i-download-a-binary-file
 */
function downloadFile(File,Source,Target){
	var Object = WScript.CreateObject('MSXML2.XMLHTTP');

	Object.Open('GET', Source, false);
	Object.Send();

	if (Object.Status != 200)
	{
		return false;
	}
	
	// Create the Data Stream
	var Stream = WScript.CreateObject('ADODB.Stream');

	// Establish the Stream
	Stream.Open();
	Stream.Type = 1; // adTypeBinary
	Stream.Write(Object.ResponseBody);
	Stream.Position = 0;

	// Create an Empty Target File
	if (File.FileExists(Target))
	{
		File.DeleteFile(Target);
	}

	// Write the Data Stream to the File
	Stream.SaveToFile(Target, 2); // adSaveCreateOverWrite
	Stream.Close();
	Object = null;
	Stream = null;
	return true;
	
}

function d(title,str){
	//WScript.Echo(title + ": " + str);
}
function e(title,str){
	WScript.Echo("ERROR! " + title + ": " + str);
	WScript.Quit();
}


function map(fso,pathFrom,pathToFoler,pathToFile){
	var pathTargetFolder = parsePath(fso, pathToFoler);
	var folder = fso.GetFolder(parsePath(fso, pathFrom));
	var folderfiles = new Enumerator(folder.Files);
	for (; !folderfiles.atEnd(); folderfiles.moveNext()){
		var pathFromFile = folderfiles.item();
		var itemname = fso.GetBaseName(pathFromFile);
		var pathSendFolder = pathTargetFolder.replace("XX",itemname).replace("values-en","values");
		var pathSendFile = pathToFile.replace("XX",itemname);
		if(!fso.FolderExists(pathSendFolder)){
			fso.CreateFolder(pathSendFolder);
		}
		d("copy",pathFromFile);
		fso.CopyFile (pathFromFile,fso.BuildPath(pathSendFolder,pathSendFile), true);
	}
}

//単純に説明すると、(Windowsにおいては、)「/」区切りを「\」区切りにする
function parsePath(fso, pathToFolder){
	var pathTarget = "";
	var arrayPath = pathToFolder.split("/");
	for (var idx in arrayPath){
		var item = arrayPath[idx];
		if(item == ""){
		} else if(pathTarget == ""){
			pathTarget = item;
		} else {
			pathTarget = fso.BuildPath(pathTarget,item);
		}
	}
	return pathTarget;
}