Localisation (C++) question.

Started by
4 comments, last by ToohrVyk 16 years, 2 months ago
Ok, I'm having this debate with a guy at work about how to handle string localisation in our software. For reasons I won't go into, we aren't using a resource file for our string localisation, we are using a series of text files. My idea is to parse in the text file and then put them in a std::map, accessing via a key, so the text file would look like this: [English] RED=Red BTERROR=Bluetooth Error Access would be like this: std::wstring myRedString = myLanguageMap ["RED"]; Ok, so far, so good. His idea, on the other hand, is to #define constants like this: #define IDS_STRING1 0 and then pass those into the map instead, like this: std::wstring myRedString = myLanguageMap [IDS_STRING1]; where the language file looks like this: [English] 0=Red His idea is that you will get a compile-time error if you try to use IDS_STRING2, because it isn't defined, whereas with my method you could write myLanguageMap ["REF"] by mistake and that would throw at runtime, rather than compile time. On the other hand, I prefer the verbosity and readabiliy of writing "RED" and also you don't have to maintain a set of constants alongside your language file. Double the work. Who is right?
Advertisement
Whatever method you choose, don't use #define to define constants in C++. There are vastly superior methods to achieve this. In this particular case, enumerations happen to be auto-numbered and type-safe.

As for the actual choice, I would be tempted to propose an additional way of doing things, which is close to how C# handles this:
// A language representation, loaded from filetypedef std::map<std::string, std::wstring> data;std::wstring read(const data &d, const std::string &k){  data::const_iterator it = d.find(k);  if (it == d.end()) return std::wstring();  return it->second;}// Auto-generated source file begins here ---struct Strings{  const std::wstring red;  const std::wstring bterror;  Strings(const data &d) :    red( read(d, "RED") ),    bterror( read(d, "BTERROR") )  {}};// Auto-generated source file ends here ---std::wstring myRedString = myLanguageMap.red;


It is then a trivial task to generate the above given a list of all possible keys (which could be read from a sample description). This provides compile-time safety (but, as a consequence, is not extensible at runtime), enables intellisense, and avoids the difficulty of maintaining two sets of constants.

That is a great idea. Kind of similar to the "my" namespace for resources in VB.NET. Thanks for your help.
I kind of like the simplicity of the approach of not using (resource) identifiers in any form (be they enums or other strings) and in the code make the string literal totally explicit.

cout << tr( "do you accept the pending connection (Y/n)") << endl;

tr() indicates a translation which uses std::map which is loaded on initialisation with the mappings that are made explicit in a file. The disadvantage is that it is not compile time checked but you have a marker in the "tr" - something on which to grep and cross reference with against the translation txt file.

I can understand others not liking this approach however without an indirection layer. I might be biased towards a too simple approach since I have to use the mess of mfc resource ids and string tables to do this stuff at the moment.
Quote:Original post by ToohrVyk
In this particular case, enumerations happen to be auto-numbered and type-safe.


I always thought the opposite was true. [PDF]

Quote:
C plus plus [C plus plus 03] currently provides only incremental improvements over C [C99] enums. Major safety and security problems remain, notably in the areas of type safety, unintended errors, code clarity, and code portability.
Quote:Original post by aaron_ds
I always thought the opposite was true. [PDF]


C++ enumerations are nowhere as safe as, say, OCaml sum types. However, they are type-safe as far as converting an integer to an enumeration goes (it cannot be done implicitely). In the current situation, there is no danger in mistakenly using an enumeration value as an integer, though there is a danger in using an integer as an enumeration value (when using integer constants instead of enumerations). The second danger is effectively solved by the requirement for explicit conversion.

This topic is closed to new replies.

Advertisement