3
votes

I would like to have an easy to use way to write code like:

#include <iostream>
int main (){
    std::cout << "hello, world!\n";
}

but that supports i18n. Here is an example using gettext():

#include <libintl.h>
#include <iostream>
int main (){
    std::cout << gettext("hello, world!\n");
}

This can then be processed by xgettext to produce a message catalog file that can be used by translators to create various versions. These extra files can be handled on target systems to allow the user to interact in a preferred language.

I would like to write the code something like this instead:

#include <i18n-iostream>
int main (){
    i18n::cout << "hello, world!\n";
}

At build time the quoted strings would be examined by a program like xgettext to produce the base message catalog file. << operator with argument i18n::cout would take a string literal as the key to lookup the run-time text to use from a message catalog.

Does it exist somewhere?

4
i18n isn't clean, unfortunately, so any language binding will have issues. Artyom makes it very clear with his example, but even English has those quirks: i18n::cout << "I have a " << my.current.object; // Fails for "apple" - should be "a_n_ apple".MSalters

4 Answers

3
votes

At build time the quoted strings would be examined by a program like xgettext to produce the base message catalog file. << operator with argument i18n::cout would take a string literal as the key to lookup the run-time text to use from a message catalog.

You try to convert a string like a single instance, but it isn't/

The point, you don't want something like this. Think of:

if(n=1)
    i18n::cout << "I need one apple"
else
    i18n::cout << "I need " << n << " apples" ;

So why this is would not work, because "n=1" or "n!=1" works only for English, many other languages have more then one plural form, also it requires translation of "I need X apples" as signle instance.

I suggest you just to learn to deal with gettext, it is quite simple and powerful, many people had thought about it.

Another point, you are usually do not call gettext but

#include <libintl.h>
#include <iostream>
#define _(x) gettext(x)

int main (){
    std::cout << _("hello, world!\n");
}

This makes the code much cleaner, also it is quite a "standard" feature to use "_" as gettext alias.

Just learn how to use it, before you try to make "nicer" API. Just to mention, gettext API is quite de-facto standard for many languages, not only C.

1
votes

The short answer is "No" :)

Seriously, which aspects of internationalization are you interested in? ICU provides pretty much everything but does not feel like standard C++. There are other libraries smaller in scope that provide some i18n functionalities, i.e. UTF-CPP for handling UTF-8 encoded strings.

1
votes

Personally I would go with this answer, but it might be possible to use a bit of streambuf magic to do this as the text is written to the stream. If you're really interested in doing this though, please take a look at Standard C++ IOStreams and Locales by Langer and Kreft, it's the bible of iostreams.

The following assumes that everything written to the buffer is to be translated, and that each full line can be translated completely:

std::string xgettext (std::string const & s)
{
  return s;
}

The following transbuf class overrides the "overflow" function and translates the buffer every time it sees a newline.

class transbuf : public std::streambuf {
public:
  transbuf (std::streambuf * realsb) : std::streambuf (), m_realsb (realsb)
    , m_buf () {}

  ~transbuf () {
    // ... flush  m_buf if necessary
  }

  virtual std::streambuf::int_type overflow (std::streambuf::int_type c) {
    m_buf.push_back (c);
    if (c == '\n') {
      // We have a complete line, translate it and write it to our stream:
      std::string transtext = xgettext (m_buf);
      for (std::string::const_iterator i = transtext.begin ()
        ; i != transtext.end ()
        ; ++i) {
        m_realsb->sputc (*i);
        // ... check that overflow returned the correct value...
      }
      m_buf = "";
    }
    return c;
  }    

  std::streambuf * get () { return m_realsb; }

  // data
private:
  std::streambuf * m_realsb;
  std::string m_buf;
};

And here's an example of how that might be used:

int main ()
{
  transbuf * buf = new transbuf (std::cout.rdbuf ());
  std::ostream trans (buf);

  trans << "Hello";  // Added to m_buf
  trans << " World"; // Added to m_buf
  trans << "\n";     // Causes m_buf to be written

  trans << "Added to buffer\neach new line causes\n"
           "the string to be translated\nand written" << std::endl;

  delete buf;
}    
0
votes

You mean you just want another API? You could write a small wrapper, shouldn't be too hard and it would give you the possibility to use the best API you can think of :)