Wednesday, February 16, 2005

2 minute guide to C++ locales

Until recently I was afraid of writing international C++ using the locales system, but actually the most common operations are very simple:

Start like this...

#include <iostream>
#include <locale>
#include <sstream>

using namespace std;

void main()
{

...then you are ready to ask what the current global locale is...

locale global = locale();
cout << "Global: " << global.name() << endl;

...in the beginning this is always the same "classic" locale (called C)...

locale classic = locale::classic();
cout << "Classic: " << classic.name() << endl;

...which means that if programs don't monkey about with their locale, they will behave the same regardless of where they are run. Apparently this is not mandated by the standard, but any other behaviour would be crazy.

Now to the main event: what is the user's runtime locale? Just use an empty string like so...

locale native("");
cout << "Native: " << native.name() << endl;

Now we know how to create all the locales we want, how do we retrieve two popular pieces of information from them? Here is the currency symbol...

const moneypunct<char>& monetaryFacet = use_facet<moneypunct<char> >(native);
cout << "Currency: " << monetaryFacet.curr_symbol() << endl;

...and here is the radix (a fancy name for decimal point)...

const numpunct<char>& numericFacet = use_facet<numpunct<char> >(native);
cout << "Radix: " << numericFacet.decimal_point() << endl;

Streams are also affected by locales: on a per-stream basis like so...

cout.imbue(classic);
cout << 2.7183 << endl;

cout.imbue(native);
cout << 2.7183 << endl;

...or globally (for all new streams)...

locale::global(classic);
ostringstream globalStream;
globalStream << 3.1415 << endl;
cout << globalStream.str();

locale::global(native);
ostringstream localStream;
localStream << 3.1415 << endl;
cout << localStream.str();

}

So that is the 2 minute workout. What about the result? Here it is for my native locale (UK)...

Global: C
Classic: C
Native: English_United Kingdom.1252
Currency: ú
Radix: .
2.7183
2.7183
3.1415
3.1415

...and here's what happens when I change my Windows location to France (from the control panel)...

Global: C
Classic: C
Native: French_France.1252
Currency: Ç
Radix: ,
2.7183
2,7183
3.1415
3,1415

...now let's go to the USA...

Global: C
Classic: C
Native: English_United States.1252
Currency: $
Radix: .
2.7183
2.7183
3.1415
3.1415

Perfect - except for the fact that ú and Ç are not the respective currencies of the UK (£) and France (€). Unfortunately these symbols are not in the terminal font, but rest-assured they would appear in any normal typeface. If you serve up console information to your customer, your biggest problem is NOT internationalization ;-)

Obviously there is much more to this subject, but I am already out of my depth. If you want to know everything (and I mean everything) about this and other stream related issues try Standard C++ IO Streams and Locales.

No comments: