views:

78

answers:

3

So, I'm working on translating my C++ app into multiple languages. What I'm currently using is something like:

#define TR(x) (lookupTranslatedString( currentLocale(), x ))
wcout << TR(L"This phrase is in English") << endl;

The translations are from a CSV file which maps the english string to the translated string.

"This phrase is in English","Nasa Tagalog itong pagsabi"

This is simplified, but that's the basic idea.

My question is about generating the list of English phrases that need to be translated. I just need the CSV with all the english phrases, and blank translated phrases. I was hoping that it might be possible to either generate this list at compile time or at runtime. At compiletime I was thinking something like this:

#define TR(x) \
    #warning x \
    (lookupTranslatedString( currentLocale(), x ))

and then maybe parse the compile log, or something. This seems not to work so well.

At runtime would also be great. I was thinking of just starting the app and having a hidden command that would dump the english CSV. I've seen similar methods used to register commands with a central list, using global variables. It might look something like this:

class TrString
{
public:
    static std::set< std::wstring > sEnglishPhrases;
    TrString( std::wstring english_phrase ) { sEnglishPhrases.insert( english_phrase ); }
};

#define TR(x) do {static TrString trstr(x);} while( false ); (lookupTranslatedString( currentLocale(), x ));

I know there's two problems with the above code. I doubt it compiles, but more importantly, in order to generate a list of all english phrases, I'd need to hit every single code path before accessing sEnglishPhrases.

It looks like I'll end up writing a small parser to read through all my code and look for TR strings, which isn't really that tough. I was just hoping to learn a little more about C++, and if there's a better way to do this.

+2  A: 

You could just build a quick script to parse the file and strip what you need.

awk '/TR\(L"[^"]*")/ {print}' plop.c

If you need somthing slightly more complex then perl is your friend.

Martin York
Yes, this is what it looks like I'll end up doing. I was just wondering if there's a better way.
tfinniga
+1  A: 

I think you're almost there. Taking the last idea:

class TrString
{
public:
    static std::set< std::string > sEnglishPhrases;
    std::string phrase;
    TrString(const std::string& english_phrase ):phrase(english_phrase)
    { sEnglishPhrases.insert( english_phrase ); }
    friend ostream &operator<<(ostream &stream, const TrString& o);
};

ostream &operator<<(ostream &stream, const TrString& o)
{
    stream << lookupTranslatedString( currentLocale(), o.phrase);
    return stream;
}

#define TR(x) ( TrString(x) )
// ...
std::cout << TR("This phrase is in English") << std::endl;

And as you say, you do need to run the code over every TR() statement, but you could configure a unit test framework to do this.

My alternative would be to use the above TrString class to make static variables for each module:

// unnamed namespace gives static instances
namespace
{
   TrString InEnglish("This phrase is in English");
   // ...
}

Now you just need to link in an alternative main() to print out TrString:: sEnglishPhrases

quamrana
It looks like this is about the best solution, which is to say that there's no really clean solution. I guess that makes sense, since everyone I've seen, from gettext to Qt all just implement a little parser.
tfinniga
A: 

What you're looking for seems very similar to what GNU gettext does. In particular, look at the xgettext tool.

Éric Malenfant