restore trunk
[supertux.git] / src / tinygettext / tinygettext.cpp
1 //  $Id$
2 //
3 //  TinyGetText
4 //  Copyright (C) 2006 Ingo Ruhnke <grumbel@gmx.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20 #include <config.h>
21
22 #include <sys/types.h>
23 #include <fstream>
24 #include <iostream>
25 #include <algorithm>
26 #include <ctype.h>
27 #include <errno.h>
28
29 #include "SDL.h"
30
31 #include "tinygettext.hpp"
32 #include "log.hpp"
33 #include "physfs/physfs_stream.hpp"
34 #include "log.hpp"
35 #include "findlocale.hpp"
36
37 //#define TRANSLATION_DEBUG
38
39 namespace TinyGetText {
40
41 /** Convert \a which is in \a from_charset to \a to_charset and return it */
42 std::string convert(const std::string& text,
43                     const std::string& from_charset,
44                     const std::string& to_charset)
45 {
46   if (from_charset == to_charset)
47     return text;
48
49   char *in = new char[text.length() + 1];
50   strcpy(in, text.c_str());
51   char *out = SDL_iconv_string(to_charset.c_str(), from_charset.c_str(), in, text.length() + 1);
52   delete[] in; 
53   if(out == 0)
54   {
55     log_warning << "Error: conversion from " << from_charset << " to " << to_charset << " failed" << std::endl;
56     return "";
57   }
58   std::string ret(out);
59   SDL_free(out);
60   return ret;
61 #if 0
62   iconv_t cd = SDL_iconv_open(to_charset.c_str(), from_charset.c_str());
63
64   size_t in_len = text.length();
65   size_t out_len = text.length()*3; // FIXME: cross fingers that this is enough
66
67   char*  out_orig = new char[out_len];
68   char*  in_orig  = new char[in_len+1];
69   strcpy(in_orig, text.c_str());
70
71   char* out = out_orig;
72   ICONV_CONST char* in  = in_orig;
73   size_t out_len_temp = out_len; // iconv is counting down the bytes it has
74                                  // written from this...
75
76   size_t retval = SDL_iconv(cd, &in, &in_len, &out, &out_len_temp);
77   out_len -= out_len_temp; // see above
78   if (retval == (size_t) -1)
79     {
80       log_warning << strerror(errno) << std::endl;
81       log_warning << "Error: conversion from " << from_charset << " to " << to_charset << " went wrong: " << retval << std::endl;
82       return "";
83     }
84   SDL_iconv_close(cd);
85
86   std::string ret(out_orig, out_len);
87   delete[] out_orig;
88   delete[] in_orig;
89   return ret;
90 #endif
91 }
92
93 bool has_suffix(const std::string& lhs, const std::string rhs)
94 {
95   if (lhs.length() < rhs.length())
96     return false;
97   else
98     return lhs.compare(lhs.length() - rhs.length(), rhs.length(), rhs) == 0;
99 }
100
101 bool has_prefix(const std::string& lhs, const std::string rhs)
102 {
103   if (lhs.length() < rhs.length())
104     return false;
105   else
106     return lhs.compare(0, rhs.length(), rhs) == 0;
107 }
108
109 int plural1(int )     { return 0; }
110 int plural2_1(int n)  { return (n != 1); }
111 int plural2_2(int n)  { return (n > 1); }
112 int plural3_lv(int n) { return (n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2); }
113 int plural3_ga(int n) { return n==1 ? 0 : n==2 ? 1 : 2; }
114 int plural3_lt(int n) { return (n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2); }
115 int plural3_1(int n)  { return (n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
116 int plural3_sk(int n) { return (n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; }
117 int plural3_pl(int n) { return (n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
118 int plural3_sl(int n) { return (n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3); }
119
120 /** Language Definitions */
121 //*{
122 LanguageDef lang_hu("hu", "Hungarian",         1, plural1); // "nplurals=1; plural=0;"
123 LanguageDef lang_ja("ja", "Japanese",          1, plural1); // "nplurals=1; plural=0;"
124 LanguageDef lang_ko("ko", "Korean",            1, plural1); // "nplurals=1; plural=0;"
125 LanguageDef lang_tr("tr", "Turkish",           1, plural1); // "nplurals=1; plural=0;"
126 LanguageDef lang_da("da", "Danish",            2, plural2_1); // "nplurals=2; plural=(n != 1);"
127 LanguageDef lang_nl("nl", "Dutch",             2, plural2_1); // "nplurals=2; plural=(n != 1);"
128 LanguageDef lang_en("en", "English",           2, plural2_1); // "nplurals=2; plural=(n != 1);"
129 LanguageDef lang_fo("fo", "Faroese",           2, plural2_1); // "nplurals=2; plural=(n != 1);"
130 LanguageDef lang_de("de", "German",            2, plural2_1); // "nplurals=2; plural=(n != 1);"
131 LanguageDef lang_nb("nb", "Norwegian Bokmal",  2, plural2_1); // "nplurals=2; plural=(n != 1);"
132 LanguageDef lang_no("no", "Norwegian",         2, plural2_1); // "nplurals=2; plural=(n != 1);"
133 LanguageDef lang_nn("nn", "Norwegian Nynorsk", 2, plural2_1); // "nplurals=2; plural=(n != 1);"
134 LanguageDef lang_sv("sv", "Swedish",           2, plural2_1); // "nplurals=2; plural=(n != 1);"
135 LanguageDef lang_et("et", "Estonian",          2, plural2_1); // "nplurals=2; plural=(n != 1);"
136 LanguageDef lang_fi("fi", "Finnish",           2, plural2_1); // "nplurals=2; plural=(n != 1);"
137 LanguageDef lang_el("el", "Greek",             2, plural2_1); // "nplurals=2; plural=(n != 1);"
138 LanguageDef lang_he("he", "Hebrew",            2, plural2_1); // "nplurals=2; plural=(n != 1);"
139 LanguageDef lang_it("it", "Italian",           2, plural2_1); // "nplurals=2; plural=(n != 1);"
140 LanguageDef lang_pt("pt", "Portuguese",        2, plural2_1); // "nplurals=2; plural=(n != 1);"
141 LanguageDef lang_es("es", "Spanish",           2, plural2_1); // "nplurals=2; plural=(n != 1);"
142 LanguageDef lang_eo("eo", "Esperanto",         2, plural2_1); // "nplurals=2; plural=(n != 1);"
143 LanguageDef lang_fr("fr", "French",            2, plural2_2); // "nplurals=2; plural=(n > 1);"
144 LanguageDef lang_pt_BR("pt_BR", "Brazilian",   2, plural2_2); // "nplurals=2; plural=(n > 1);"
145 LanguageDef lang_lv("lv", "Latvian",           3, plural3_lv); // "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);"
146 LanguageDef lang_ga("ga", "Irish",             3, plural3_ga); // "nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2;"
147 LanguageDef lang_lt("lt", "Lithuanian",        3, plural3_lt); // "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);"
148 LanguageDef lang_hr("hr", "Croatian",          3, plural3_1); // "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"
149 LanguageDef lang_cs("cs", "Czech",             3, plural3_1); // "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"
150 LanguageDef lang_ru("ru", "Russian",           3, plural3_1); // "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"
151 LanguageDef lang_uk("uk", "Ukrainian",         3, plural3_1); // "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"
152 LanguageDef lang_sk("sk", "Slovak",            3, plural3_sk); // "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;"
153 LanguageDef lang_pl("pl", "Polish",            3, plural3_pl); // "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);
154 LanguageDef lang_sl("sl", "Slovenian",         3, plural3_sl); // "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);"
155 //*}
156
157 LanguageDef&
158 get_language_def(const std::string& name)
159 {
160   if (name == "hu") return lang_hu;
161   else if (name == "ja") return lang_ja;
162   else if (name == "ko") return lang_ko;
163   else if (name == "tr") return lang_tr;
164   else if (name == "da") return lang_da;
165   else if (name == "nl") return lang_nl;
166   else if (name == "en") return lang_en;
167   else if (name == "fo") return lang_fo;
168   else if (name == "de") return lang_de;
169   else if (name == "nb") return lang_nb;
170   else if (name == "no") return lang_no;
171   else if (name == "nn") return lang_nn;
172   else if (name == "sv") return lang_sv;
173   else if (name == "et") return lang_et;
174   else if (name == "fi") return lang_fi;
175   else if (name == "el") return lang_el;
176   else if (name == "he") return lang_he;
177   else if (name == "it") return lang_it;
178   else if (name == "pt") return lang_pt;
179   else if (name == "es") return lang_es;
180   else if (name == "eo") return lang_eo;
181   else if (name == "fr") return lang_fr;
182   else if (name == "pt_BR") return lang_pt_BR;
183   else if (name == "lv") return lang_lv;
184   else if (name == "ga") return lang_ga;
185   else if (name == "lt") return lang_lt;
186   else if (name == "hr") return lang_hr;
187   else if (name == "cs") return lang_cs;
188   else if (name == "ru") return lang_ru;
189   else if (name == "uk") return lang_uk;
190   else if (name == "sk") return lang_sk;
191   else if (name == "pl") return lang_pl;
192   else if (name == "sl") return lang_sl;
193   else return lang_en;
194 }
195
196 DictionaryManager::DictionaryManager()
197   : current_dict(&empty_dict)
198 {
199   parseLocaleAliases();
200   // Environment variable SUPERTUX_LANG overrides language settings.
201   const char* lang = getenv( "SUPERTUX_LANG" );
202   if( lang ){
203     set_language( lang );
204     return;
205   }
206   // use findlocale to setup language
207   FL_Locale *locale;
208   FL_FindLocale( &locale, FL_MESSAGES );
209   if(locale->lang) {
210     if (locale->country) {
211       set_language( std::string(locale->lang)+"_"+std::string(locale->country) );
212     } else {
213       set_language( std::string(locale->lang) );
214     }
215   }
216   FL_FreeLocale( &locale );
217 }
218
219 void
220 DictionaryManager::parseLocaleAliases()
221 {
222   // try to parse language alias list
223   std::ifstream in("/usr/share/locale/locale.alias");
224
225   char c = ' ';
226   while(in.good() && !in.eof()) {
227     while(isspace(c) && !in.eof())
228       in.get(c);
229
230     if(c == '#') { // skip comments
231       while(c != '\n' && !in.eof())
232         in.get(c);
233       continue;
234     }
235
236     std::string alias;
237     while(!isspace(c) && !in.eof()) {
238       alias += c;
239       in.get(c);
240     }
241     while(isspace(c) && !in.eof())
242       in.get(c);
243     std::string language;
244     while(!isspace(c) && !in.eof()) {
245       language += c;
246       in.get(c);
247     }
248
249     if(in.eof())
250       break;
251     set_language_alias(alias, language);
252   }
253 }
254
255 Dictionary&
256 DictionaryManager::get_dictionary(const std::string& spec)
257 {
258
259   //log_debug << "Dictionary for language \"" << spec << "\" requested" << std::endl;
260
261   std::string lang = get_language_from_spec(spec);
262
263   //log_debug << "...normalized as \"" << lang << "\"" << std::endl;
264
265   Dictionaries::iterator i = dictionaries.find(get_language_from_spec(lang));
266   if (i != dictionaries.end())
267     {
268       return i->second;
269     }
270   else // Dictionary for languages lang isn't loaded, so we load it
271     {
272       //log_debug << "get_dictionary: " << lang << std::endl;
273       Dictionary& dict = dictionaries[lang];
274
275       dict.set_language(get_language_def(lang));
276       if(charset != "")
277         dict.set_charset(charset);
278
279       for (SearchPath::iterator p = search_path.begin(); p != search_path.end(); ++p)
280         {
281           char** files = PHYSFS_enumerateFiles(p->c_str());
282           if(!files)
283             {
284               log_warning << "Error: enumerateFiles() failed on " << *p << std::endl;
285             }
286           else
287             {
288               for(const char* const* filename = files;
289                       *filename != 0; filename++) {
290
291                 // check if filename matches requested language
292                 std::string fname = std::string(*filename);
293                 std::string load_from_file = "";
294                 if(fname == lang + ".po") {
295                   load_from_file = fname;
296                 } else {
297                   std::string::size_type s = lang.find("_");
298                   if(s != std::string::npos) {
299                     std::string lang_short = std::string(lang, 0, s);
300                     if (fname == lang_short + ".po") {
301                       load_from_file = lang_short;
302                     }
303                   }
304                 }
305
306                 // if it matched, load dictionary
307                 if (load_from_file != "") {
308                   //log_debug << "Loading dictionary for language \"" << lang << "\" from \"" << filename << "\"" << std::endl;
309                   std::string pofile = *p + "/" + *filename;
310                   try {
311                       IFileStream in(pofile);
312                       read_po_file(dict, in);
313                   } catch(std::exception& e) {
314                       log_warning << "Error: Failure file opening: " << pofile << std::endl;
315                       log_warning << e.what() << "" << std::endl;
316                   }
317                 }
318
319               }
320               PHYSFS_freeList(files);
321             }
322         }
323
324       return dict;
325     }
326 }
327
328 std::set<std::string>
329 DictionaryManager::get_languages()
330 {
331   std::set<std::string> languages;
332
333   for (SearchPath::iterator p = search_path.begin(); p != search_path.end(); ++p)
334     {
335       char** files = PHYSFS_enumerateFiles(p->c_str());
336       if (!files)
337         {
338           log_warning << "Error: opendir() failed on " << *p << std::endl;
339         }
340       else
341         {
342           for(const char* const* file = files; *file != 0; file++) {
343               if(has_suffix(*file, ".po")) {
344                   std::string filename = *file;
345                   languages.insert(filename.substr(0, filename.length()-3));
346               }
347           }
348           PHYSFS_freeList(files);
349         }
350     }
351   return languages;
352 }
353
354 void
355 DictionaryManager::set_language(const std::string& lang)
356 {
357   //log_debug << "set_language \"" << lang << "\"" << std::endl;
358   language = get_language_from_spec(lang);
359   //log_debug << "==> \"" << language << "\"" << std::endl;
360   current_dict = & (get_dictionary(language));
361 }
362
363 const std::string&
364 DictionaryManager::get_language() const
365 {
366   return language;
367 }
368
369 void
370 DictionaryManager::set_charset(const std::string& charset)
371 {
372   dictionaries.clear(); // changing charset invalidates cache
373   this->charset = charset;
374   set_language(language);
375 }
376
377 void
378 DictionaryManager::set_language_alias(const std::string& alias,
379     const std::string& language)
380 {
381   language_aliases.insert(std::make_pair(alias, language));
382 }
383
384 std::string
385 DictionaryManager::get_language_from_spec(const std::string& spec)
386 {
387   std::string lang = spec;
388   Aliases::iterator i = language_aliases.find(lang);
389   if(i != language_aliases.end()) {
390     lang = i->second;
391   }
392
393   std::string::size_type s = lang.find(".");
394   if(s != std::string::npos) {
395     lang = std::string(lang, 0, s);
396   }
397
398   s = lang.find("_");
399   if(s == std::string::npos) {
400     std::string lang_big = lang;
401     std::transform (lang_big.begin(), lang_big.end(), lang_big.begin(), toupper);
402     lang += "_" + lang_big;
403   }
404
405   return lang;
406
407 }
408
409 void
410 DictionaryManager::add_directory(const std::string& pathname)
411 {
412   dictionaries.clear(); // adding directories invalidates cache
413   search_path.push_back(pathname);
414   set_language(language);
415 }
416
417 //---------------------------------------------------------------------------
418
419 Dictionary::Dictionary(const LanguageDef& language_, const std::string& charset_)
420   : language(language_), charset(charset_)
421 {
422 }
423
424 Dictionary::Dictionary()
425   : language(lang_en)
426 {
427 }
428
429 std::string
430 Dictionary::get_charset() const
431 {
432   return charset;
433 }
434
435 void
436 Dictionary::set_charset(const std::string& charset_)
437 {
438   charset = charset_;
439 }
440
441 void
442 Dictionary::set_language(const LanguageDef& lang)
443 {
444   language = lang;
445 }
446
447 std::string
448 Dictionary::translate(const std::string& msgid, const std::string& msgid2, int num)
449 {
450   PluralEntries::iterator i = plural_entries.find(msgid);
451   std::map<int, std::string>& msgstrs = i->second;
452
453   if (i != plural_entries.end() && !msgstrs.empty())
454     {
455       int g = language.plural(num);
456       std::map<int, std::string>::iterator j = msgstrs.find(g);
457       if (j != msgstrs.end())
458         {
459           return j->second;
460         }
461       else
462         {
463           // Return the first translation, in case we can't translate the specific number
464           return msgstrs.begin()->second;
465         }
466     }
467   else
468     {
469 #ifdef TRANSLATION_DEBUG
470       log_warning << "Couldn't translate: " << msgid << std::endl;
471       log_warning << "Candidates: " << std::endl;
472       for (PluralEntries::iterator i = plural_entries.begin(); i != plural_entries.end(); ++i)
473         log_debug << "'" << i->first << "'" << std::endl;
474 #endif
475
476       if (plural2_1(num)) // default to english rules
477         return msgid2;
478       else
479         return msgid;
480     }
481 }
482
483 const char*
484 Dictionary::translate(const char* msgid)
485 {
486   Entries::iterator i = entries.find(msgid);
487   if (i != entries.end() && !i->second.empty())
488     {
489       return i->second.c_str();
490     }
491   else
492     {
493 #ifdef TRANSLATION_DBEUG
494       log_warning << "Couldn't translate: " << msgid << std::endl;
495 #endif
496       return msgid;
497     }
498 }
499
500 std::string
501 Dictionary::translate(const std::string& msgid)
502 {
503   Entries::iterator i = entries.find(msgid);
504   if (i != entries.end() && !i->second.empty())
505     {
506       return i->second;
507     }
508   else
509     {
510 #ifdef TRANSLATION_DBEUG
511       log_warning << "Couldn't translate: " << msgid << std::endl;
512 #endif
513       return msgid;
514     }
515 }
516
517 void
518 Dictionary::add_translation(const std::string& msgid, const std::string& ,
519                             const std::map<int, std::string>& msgstrs)
520 {
521   // Do we need msgid2 for anything? its after all supplied to the
522   // translate call, so we just throw it away
523   plural_entries[msgid] = msgstrs;
524 }
525
526 void
527 Dictionary::add_translation(const std::string& msgid, const std::string& msgstr)
528 {
529   entries[msgid] = msgstr;
530 }
531
532 class POFileReader
533 {
534 private:
535   struct Token
536   {
537     std::string keyword;
538     std::string content;
539   };
540
541   Dictionary& dict;
542
543   std::string from_charset;
544   std::string to_charset;
545
546   std::string current_msgid;
547   std::string current_msgid_plural;
548   std::map<int, std::string> msgstr_plural;
549
550   int line_num;
551
552   enum { WANT_MSGID, WANT_MSGSTR, WANT_MSGSTR_PLURAL, WANT_MSGID_PLURAL } state;
553
554 public:
555   POFileReader(std::istream& in, Dictionary& dict_)
556     : dict(dict_)
557   {
558     state = WANT_MSGID;
559     line_num = 0;
560     char c = in.get();
561     if(c == (char) 0xef) { // skip UTF-8 intro that some texteditors produce
562         in.get();
563         in.get();
564     } else {
565         in.unget();
566     }
567     tokenize_po(in);
568   }
569
570   void parse_header(const std::string& header)
571   {
572     // Seperate the header in lines
573     typedef std::vector<std::string> Lines;
574     Lines lines;
575
576     std::string::size_type start = 0;
577     for(std::string::size_type i = 0; i < header.length(); ++i)
578       {
579         if (header[i] == '\n')
580           {
581             lines.push_back(header.substr(start, i - start));
582             start = i+1;
583           }
584       }
585
586     for(Lines::iterator i = lines.begin(); i != lines.end(); ++i)
587       {
588         if (has_prefix(*i, "Content-Type: text/plain; charset=")) {
589           from_charset = i->substr(strlen("Content-Type: text/plain; charset="));
590         }
591       }
592
593     if (from_charset.empty() || from_charset == "CHARSET")
594       {
595         log_warning << "Error: Charset not specified for .po, fallback to ISO-8859-1" << std::endl;
596         from_charset = "ISO-8859-1";
597       }
598
599     to_charset = dict.get_charset();
600     if (to_charset.empty())
601       { // No charset requested from the dict, use utf-8
602         to_charset = "utf-8";
603         dict.set_charset(from_charset);
604       }
605   }
606
607   void add_token(const Token& token)
608   {
609     switch(state)
610       {
611       case WANT_MSGID:
612         if (token.keyword == "msgid")
613           {
614             current_msgid = token.content;
615             state = WANT_MSGID_PLURAL;
616           }
617         else if (token.keyword.empty())
618           {
619             //log_warning << "Got EOF, everything looks ok." << std::endl;
620           }
621         else
622           {
623             log_warning << "tinygettext: expected 'msgid' keyword, got " << token.keyword << " at line " << line_num << std::endl;
624           }
625         break;
626
627       case WANT_MSGID_PLURAL:
628         if (token.keyword == "msgid_plural")
629           {
630             current_msgid_plural = token.content;
631             state = WANT_MSGSTR_PLURAL;
632           }
633         else
634           {
635             state = WANT_MSGSTR;
636             add_token(token);
637           }
638         break;
639
640       case WANT_MSGSTR:
641         if (token.keyword == "msgstr")
642           {
643             if (current_msgid == "")
644               { // .po Header is hidden in the msgid with the empty string
645                 parse_header(token.content);
646               }
647             else
648               {
649                 dict.add_translation(current_msgid, convert(token.content, from_charset, to_charset));
650               }
651             state = WANT_MSGID;
652           }
653         else
654           {
655             log_warning << "tinygettext: expected 'msgstr' keyword, got " << token.keyword << " at line " << line_num << std::endl;
656           }
657         break;
658
659       case WANT_MSGSTR_PLURAL:
660         if (has_prefix(token.keyword, "msgstr["))
661           {
662             int num;
663             if (sscanf(token.keyword.c_str(), "msgstr[%d]", &num) != 1)
664               {
665                 log_warning << "Error: Couldn't parse: " << token.keyword << std::endl;
666               }
667             else
668               {
669                 msgstr_plural[num] = convert(token.content, from_charset, to_charset);
670               }
671           }
672         else
673           {
674             dict.add_translation(current_msgid, current_msgid_plural, msgstr_plural);
675
676             state = WANT_MSGID;
677             add_token(token);
678           }
679         break;
680       }
681   }
682
683   inline int getchar(std::istream& in)
684   {
685     int c = in.get();
686     if (c == '\n')
687       line_num += 1;
688     return c;
689   }
690
691   void tokenize_po(std::istream& in)
692   {
693     enum State { READ_KEYWORD,
694                  READ_CONTENT,
695                  READ_CONTENT_IN_STRING,
696                  SKIP_COMMENT };
697
698     State state = READ_KEYWORD;
699     int c;
700     Token token;
701
702     while((c = getchar(in)) != EOF)
703       {
704         //log_debug << "Lexing char: " << char(c) << " " << state << std::endl;
705         switch(state)
706           {
707           case READ_KEYWORD:
708             if (c == '#')
709               {
710                 state = SKIP_COMMENT;
711               }
712             else
713               {
714                 // Read a new token
715                 token = Token();
716
717                 do { // Read keyword
718                   token.keyword += c;
719                 } while((c = getchar(in)) != EOF && !isspace(c));
720                 in.unget();
721
722                 state = READ_CONTENT;
723               }
724             break;
725
726           case READ_CONTENT:
727             while((c = getchar(in)) != EOF)
728               {
729                 if (c == '"') {
730                   // Found start of content
731                   state = READ_CONTENT_IN_STRING;
732                   break;
733                 } else if (isspace(c)) {
734                   // skip
735                 } else { // Read something that may be a keyword
736                   in.unget();
737                   state = READ_KEYWORD;
738                   add_token(token);
739                   break;
740                 }
741               }
742             break;
743
744           case READ_CONTENT_IN_STRING:
745             if (c == '\\') {
746               c = getchar(in);
747               if (c != EOF)
748                 {
749                   if (c == 'n') token.content += '\n';
750                   else if (c == 't') token.content += '\t';
751                   else if (c == 'r') token.content += '\r';
752                   else if (c == '"') token.content += '"';
753                   else if (c == '\\') token.content += '\\';
754                   else
755                     {
756                       log_warning << "Unhandled escape character: " << char(c) << std::endl;
757                     }
758                 }
759               else
760                 {
761                   log_warning << "Unterminated string" << std::endl;
762                 }
763             } else if (c == '"') { // Content string is terminated
764               state = READ_CONTENT;
765             } else {
766               token.content += c;
767             }
768             break;
769
770           case SKIP_COMMENT:
771             if (c == '\n')
772               state = READ_KEYWORD;
773             break;
774           }
775       }
776     add_token(token);
777   }
778 };
779
780 void read_po_file(Dictionary& dict_, std::istream& in)
781 {
782   POFileReader reader(in, dict_);
783 }
784
785 } // namespace TinyGetText
786
787 /* EOF */