cocos2d-xでNSLocalizedStringのようなローカライズ

cocos2d-xで多言語対応させたい場合、iOSのNSLocalizedStringのようなクラスが欲しくなるのだけど、そうしたクラスは標準では用意されていないようだ。
そこで自前で用意する。

【cocos2dx】アプリをローカライズさせて海外にも売り込もう! | albatrus.com

こちらのブログを参考にさせて頂きました。
ありがとうございます。

ただ、ここに記述されたコードでは、ローカライズ対象の文章に ” を含めたい場合に、上手くいかない場合がある。
たとえば "ABC" のように、""で囲ったテキストを使いたい場合。
Localizable.stringsに "key" = ""ABC""; と記述したいところだけど、これだと取り出されるテキストは ABC となってしまう。
"key" = "value";のvalueの両端にある"はカットされてしまうのだ。

これでは都合が悪かったので、上記のコードを元に自前で作成してみたコードが以下。

CCLocalizedString.h

class CCLocalizedString
{
public:
    static void init();
    static const char *localizedString(const char* searchKey, const char* comment);
};


CCLocalizedString.cpp

#include "CCLocalizedString.h"
#include "cocos2d.h"
#include <map>

USING_NS_CC;
using namespace std;

static map<string, string> localizable;
static bool initialyzed = false;

void CCLocalizedString::init() {
    if(initialyzed) {
        return;
    }
    initialyzed = true;
    
    // 言語ディレクトリを指定する
    string file;
    ccLanguageType lang = CCApplication::sharedApplication()->getCurrentLanguage();
    switch (lang) {
        case kLanguageJapanese:
            file = "ja.lproj";
            break;
        default:
            file = "en.lproj";
            break;
    }
    file += "/Localizable.strings";

    // ファイルパス
    string fullPath = CCFileUtils::sharedFileUtils()->fullPathForFilename(file.c_str());
    
    // ファイルデータ取得
    unsigned long fileSize = 0;
    unsigned char * fileContents = CCFileUtils::sharedFileUtils()->getFileData( fullPath.c_str( ) , "rb", &fileSize );
    if (fileContents == NULL) {
        return;
    }
    
    string line, subStr;
    bool isComment = false;
    
    // 1行ずつ解析
    istringstream fileStringStream( (char*)fileContents );
    while ( getline( fileStringStream, line ) ) {
        // 先頭と末尾のスペースを除去
        line.erase(0, line.find_first_not_of(" \t"));
        line.erase(line.find_last_not_of(" \t") + 1);
        
        if (!isComment) {
            // //で始まる行はコメント
            subStr = line.substr(0, 2);
            if (subStr.compare("//") == 0) {
                continue;
            }
            
            // /* で始まる行は複数行コメント
            else if (subStr.compare("/*") == 0) {
                isComment = true;
            }
        }
        
        // 複数行コメント中
        string::size_type len = line.length();
        if (isComment) {
            // 末尾が */ で終わっているならコメント終了
            if (len >= 2) {
                subStr = line.substr(len - 2);
                if (subStr.compare("*/") == 0) {
                    isComment = false;
                }
            }
            continue;
        }
        
        // 最初の = でキーと値に分割する
        string::size_type pos = line.find_first_of('=', 0);
        if (pos != string::npos) {
            string keyStr = line.substr(0, pos);
            string subStr = line.substr(pos + 1, len - 1);
            
            // キーを囲む " を除去("の外側にあるスペース等も同時に除去)
            pos = keyStr.find_first_of("\"");
            if (pos != string::npos) {
                keyStr.erase(0, pos + 1);
            }
            pos = keyStr.find_last_of("\"");
            if (pos != string::npos) {
                keyStr.erase(pos);
            }
            
            // 値を囲む " を除去("の外側にあるスペースや;等も同時に除去)
            pos = subStr.find_first_of("\"");
            if (pos != string::npos) {
                subStr.erase(0, pos + 1);
            }
            pos = subStr.find_last_of("\"");
            if (pos != string::npos) {
                subStr.erase(pos);
            }
            
            // \nを改行コードに
            do {
                pos = subStr.find("\\n");
                if (pos != string::npos) {
                    subStr.replace(pos, 2, "\n");
                }
                else {
                    break;
                }
            }
            while (true);
            
            // \"を"に
            do {
                pos = subStr.find("\\\"");
                if (pos != string::npos) {
                    subStr.replace(pos, 2, "\"");
                }
                else {
                    break;
                }
            }
            while (true);
            
            localizable.insert(pair<string, string>(keyStr,subStr));
        }
    }
    
    delete [] fileContents;
    fileContents = NULL;
}

const char *CCLocalizedString::localizedString(const char* searchKey, const char* comment)
{
    init();
    
    map<string, string>::iterator itr = localizable.find(string(searchKey));
    if (itr != localizable.end()) {
        return (itr->second).c_str();
    }
    return comment;
}


使い方はalbatrus.comさんのコードと同じです。
仕様としては、

  1. Localizable.stringsを1行ずつパースします。
    1行ごとに"key" = "value";で記述してください。

  2. 各行の先頭と末尾のスペースはカットします。

  3. コメントアウトに対応しています。
    // で始まる行と、 /* で始まる行から */ で終わる行までを無視します。

  4. keyとvalueは、最初の = で分割します。
    なので、keyに=を含めることはできません。
    valueには = を含めても問題ありません。

  5. 末尾の ; は有っても無くても同じです。
    keyとvalueを""で囲む必要もありません。
    "key" = "value";(標準)も、"key" = "value"(末尾に;なし)も、key = value""で囲まない)もkey=value(=の前後にスペース無し)もすべて同じです。

  6. "はそのまま使えます。
    "key" = ""ABC"";"key" = "ABC"EFG";も問題ありません。

  7. "をそのまま使うのが気持ち悪い人は\"にも対応しています。
    "key" = "\"ABC\"";"key" = "ABC\"EFG";のように。

  8. 事前にパースしておきたいケースもあるだろうと、CCLocalizedString::init()を用意しました。
    明示的に使わない場合は、最初にCCLocalizedString::localizedStringを呼び出したときに自動で呼ばれます。

以上です。
見よう見まねで作ったので、なにか不備等あれば教えてください。

コメントを残す