views:

159

answers:

2

Hello, I want to localize a program I already written.. It's fairly big (almost 50k lines) and ideally I want a system that allows me (the programmer) to do the least amount of work possible, and without major changes to the program - if possible none at all.

I looked at gettext() and liked it a lot, but it's unclear to me how it would translate strings such as these:

const char *Colors[] = {
 { "Red" },
 { "Blue" },
 { "Yellow" },
 ....
};

which are VERY common in my program.. Here replacing "Red" with gettext("Red") would obviously not work.

So I thought I would do something like, OutputFunction(gettext(Colors[Id])), but then how can I get a list of strings to localize? I doubt any program is smart enough to be able to get "Red", "Blue", "Yellow" from that in a to-localize list statically.

Since it's basically a server there is no need for the ability to change the language without recompiling (I can compile it for every supported language without any major problem or annoyance), I thought about C++0x's constexpr, which would be perfect! It would work in arrays/etc and I would easily get a list of strings to localize at compile time.. Too bad that no compiler implemented it yet.

Changing all the strings to an ID is not an option since it would require a massive amount of work on my part and especially creating a new id for every new string would be annoying as hell. The same applies to converting all the arrays like the one above to something else.

So, any ideas? :/

+1  A: 

For your specific example, I might try something like:

// presumably globals
const char *Colors_en[] = {
 { "Red" },
 { "Blue" },
 { "Yellow" },
 ....
};
const char *Colors[] = {0};

// in main()
gettextarray(Colors_en, Colors, sizeof(Colors_en) / sizeof(char*));

gettextarray calls gettext on each input and writes an output. I think it could be implemented just as a call to std::transform. And you could avoid the size parameter with a bit of template trickery.

Another option is to call gettext at the point where any of the color strings is about to be used (displayed, or appended to a string for display). That means changing more code, but doesn't require that main() translates every set of strings in the program prior to doing anything that might use them.

If you don't want to do the work in main, you could do it in the code which uses the strings, something like this:

if (Colors[0] == 0)
  gettextarray(Colors_en, Colors, sizeof(Colors_en) / sizeof(char*));

Or if your app is multithreaded, consider pthread_once or the equivalent in the thread API you use.

Steve Jessop
A: 

After a lot of playing around with gettext() and xgettext I think I found a way myself (sorry onebyone but I didn't like your approach.. There must be hundreds of arrays like that and I would have to import all of them in main(), that's a lot of extern and a lot of extra work :/).

Anyways, this is how I think it can theoretically be done (I haven't tried yet to actually translate but I don't see why it wouldn't work)

Two #define's:

#define _ gettext
#define __(x) x

Then you use _ to actually translate and __ to simply mark strings as "to be translated":

const char *Colors[] = {
 { __("Red") },
 { __("Blue") },
 { __("Yellow") },
 ....
};

void PrintColor(int id) {
    cout << _("The color is: ") << _(Colors[id]);
}

Then you run:

xgettext -k_ -k__ *.cpp

And you get the following .po file:

#: test.cpp:2
msgid "Red"
msgstr ""

#: test.cpp:3
msgid "Blue"
msgstr ""

#: test.cpp:4
msgid "Yellow"
msgstr ""

#: test.cpp:9
msgid "The color is: "
msgstr ""

So, you use __ (or any other name, doesn't really matter) as a "dummy function" to just let xgettext know that the string needs to be translated, and _ to actually call gettext().

If you call _ with a string then the string will be marked to-be-translated as well, if you call it with a variable, array, whatever then it appears to be simply ignored by xgettext.

Great! Now all I have to do is go through 5 trillion files and add underscores around, as if I was a monkey :/

Andreas Bonini