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