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