Fix another small logic error
[supertux.git] / src / tinygettext / tinygettext.cpp
index 0d3ec23..e780ded 100644 (file)
@@ -1,7 +1,7 @@
 //  $Id$
-// 
-//  TinyGetText - A small flexible gettext() replacement
-//  Copyright (C) 2004 Ingo Ruhnke <grumbel@gmx.de>
+//
+//  TinyGetText
+//  Copyright (C) 2006 Ingo Ruhnke <grumbel@gmx.de>
 //
 //  This program is free software; you can redistribute it and/or
 //  modify it under the terms of the GNU General Public License
 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 //  GNU General Public License for more details.
-// 
+//
 //  You should have received a copy of the GNU General Public License
 //  along with this program; if not, write to the Free Software
 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
 #include <config.h>
 
 #include <sys/types.h>
-#include <iconv.h>
 #include <fstream>
 #include <iostream>
+#include <algorithm>
 #include <ctype.h>
 #include <errno.h>
 
+#include <SDL.h>
+
+#ifndef GP2X
+#include <SDL_stdinc.h>
+#endif
+
 #include "tinygettext.hpp"
 #include "log.hpp"
 #include "physfs/physfs_stream.hpp"
-#include "log.hpp"
+#include "findlocale.hpp"
 
 //#define TRANSLATION_DEBUG
 
@@ -39,11 +46,29 @@ std::string convert(const std::string& text,
                     const std::string& from_charset,
                     const std::string& to_charset)
 {
+#ifndef GP2X
   if (from_charset == to_charset)
     return text;
 
-  iconv_t cd = iconv_open(to_charset.c_str(), from_charset.c_str());
-  
+  char *in = new char[text.length() + 1];
+  strcpy(in, text.c_str());
+  char *out = SDL_iconv_string(to_charset.c_str(), from_charset.c_str(), in, text.length() + 1);
+  delete[] in; 
+  if(out == 0)
+  {
+    log_warning << "Error: conversion from " << from_charset << " to " << to_charset << " failed" << std::endl;
+    return "";
+  }
+  std::string ret(out);
+  SDL_free(out);
+  return ret;
+#else
+  log_warning << "FIXME: Char conversion not supported on GP2X!" << std::endl;
+  return "";
+#endif
+#if 0
+  iconv_t cd = SDL_iconv_open(to_charset.c_str(), from_charset.c_str());
+
   size_t in_len = text.length();
   size_t out_len = text.length()*3; // FIXME: cross fingers that this is enough
 
@@ -56,7 +81,7 @@ std::string convert(const std::string& text,
   size_t out_len_temp = out_len; // iconv is counting down the bytes it has
                                  // written from this...
 
-  size_t retval = iconv(cd, &in, &in_len, &out, &out_len_temp);
+  size_t retval = SDL_iconv(cd, &in, &in_len, &out, &out_len_temp);
   out_len -= out_len_temp; // see above
   if (retval == (size_t) -1)
     {
@@ -64,12 +89,13 @@ std::string convert(const std::string& text,
       log_warning << "Error: conversion from " << from_charset << " to " << to_charset << " went wrong: " << retval << std::endl;
       return "";
     }
-  iconv_close(cd);
+  SDL_iconv_close(cd);
 
   std::string ret(out_orig, out_len);
   delete[] out_orig;
   delete[] in_orig;
   return ret;
+#endif
 }
 
 bool has_suffix(const std::string& lhs, const std::string rhs)
@@ -172,22 +198,30 @@ get_language_def(const std::string& name)
   else if (name == "sk") return lang_sk;
   else if (name == "pl") return lang_pl;
   else if (name == "sl") return lang_sl;
-  else return lang_en; 
+  else return lang_en;
 }
 
 DictionaryManager::DictionaryManager()
   : current_dict(&empty_dict)
 {
   parseLocaleAliases();
-  // setup language from environment vars
-  const char* lang = getenv("LC_ALL");
-  if(!lang)
-    lang = getenv("LC_MESSAGES");
-  if(!lang)
-    lang = getenv("LANG");
-  
-  if(lang)
-    set_language(lang);
+  // Environment variable SUPERTUX_LANG overrides language settings.
+  const char* lang = getenv( "SUPERTUX_LANG" );
+  if( lang ){
+    set_language( lang );
+    return;
+  }
+  // use findlocale to setup language
+  FL_Locale *locale;
+  FL_FindLocale( &locale, FL_MESSAGES );
+  if(locale->lang) {
+    if (locale->country) {
+      set_language( std::string(locale->lang)+"_"+std::string(locale->country) );
+    } else {
+      set_language( std::string(locale->lang) );
+    }
+  }
+  FL_FreeLocale( &locale );
 }
 
 void
@@ -195,27 +229,27 @@ DictionaryManager::parseLocaleAliases()
 {
   // try to parse language alias list
   std::ifstream in("/usr/share/locale/locale.alias");
-  
+
   char c = ' ';
   while(in.good() && !in.eof()) {
-    while(isspace(c) && !in.eof())
+    while(isspace(static_cast<unsigned char>(c)) && !in.eof())
       in.get(c);
-    
+
     if(c == '#') { // skip comments
       while(c != '\n' && !in.eof())
         in.get(c);
       continue;
     }
-    
+
     std::string alias;
-    while(!isspace(c) && !in.eof()) {
+    while(!isspace(static_cast<unsigned char>(c)) && !in.eof()) {
       alias += c;
       in.get(c);
     }
-    while(isspace(c) && !in.eof())
+    while(isspace(static_cast<unsigned char>(c)) && !in.eof())
       in.get(c);
     std::string language;
-    while(!isspace(c) && !in.eof()) {
+    while(!isspace(static_cast<unsigned char>(c)) && !in.eof()) {
       language += c;
       in.get(c);
     }
@@ -225,11 +259,17 @@ DictionaryManager::parseLocaleAliases()
     set_language_alias(alias, language);
   }
 }
-  
+
 Dictionary&
 DictionaryManager::get_dictionary(const std::string& spec)
 {
+
+  //log_debug << "Dictionary for language \"" << spec << "\" requested" << std::endl;
+
   std::string lang = get_language_from_spec(spec);
+
+  //log_debug << "...normalized as \"" << lang << "\"" << std::endl;
+
   Dictionaries::iterator i = dictionaries.find(get_language_from_spec(lang));
   if (i != dictionaries.end())
     {
@@ -247,7 +287,7 @@ DictionaryManager::get_dictionary(const std::string& spec)
       for (SearchPath::iterator p = search_path.begin(); p != search_path.end(); ++p)
         {
           char** files = PHYSFS_enumerateFiles(p->c_str());
-          if(!files) 
+          if(!files)
             {
               log_warning << "Error: enumerateFiles() failed on " << *p << std::endl;
             }
@@ -255,7 +295,25 @@ DictionaryManager::get_dictionary(const std::string& spec)
             {
               for(const char* const* filename = files;
                       *filename != 0; filename++) {
-                if(std::string(*filename) == lang + ".po") {
+
+                // check if filename matches requested language
+                std::string fname = std::string(*filename);
+                std::string load_from_file = "";
+                if(fname == lang + ".po") {
+                  load_from_file = fname;
+                } else {
+                  std::string::size_type s = lang.find("_");
+                  if(s != std::string::npos) {
+                    std::string lang_short = std::string(lang, 0, s);
+                    if (fname == lang_short + ".po") {
+                      load_from_file = lang_short;
+                    }
+                  }
+                }
+
+                // if it matched, load dictionary
+                if (load_from_file != "") {
+                  //log_debug << "Loading dictionary for language \"" << lang << "\" from \"" << filename << "\"" << std::endl;
                   std::string pofile = *p + "/" + *filename;
                   try {
                       IFileStream in(pofile);
@@ -265,6 +323,7 @@ DictionaryManager::get_dictionary(const std::string& spec)
                       log_warning << e.what() << "" << std::endl;
                   }
                 }
+
               }
               PHYSFS_freeList(files);
             }
@@ -296,14 +355,16 @@ DictionaryManager::get_languages()
           }
           PHYSFS_freeList(files);
         }
-    }  
+    }
   return languages;
 }
 
 void
 DictionaryManager::set_language(const std::string& lang)
 {
+  //log_debug << "set_language \"" << lang << "\"" << std::endl;
   language = get_language_from_spec(lang);
+  //log_debug << "==> \"" << language << "\"" << std::endl;
   current_dict = & (get_dictionary(language));
 }
 
@@ -336,12 +397,21 @@ DictionaryManager::get_language_from_spec(const std::string& spec)
   if(i != language_aliases.end()) {
     lang = i->second;
   }
-  
-  std::string::size_type s = lang.find_first_of("_.");
-  if(s == std::string::npos)
-    return lang;
 
-  return std::string(lang, 0, s);  
+  std::string::size_type s = lang.find(".");
+  if(s != std::string::npos) {
+    lang = std::string(lang, 0, s);
+  }
+
+  s = lang.find("_");
+  if(s == std::string::npos) {
+    std::string lang_big = lang;
+    std::transform (lang_big.begin(), lang_big.end(), lang_big.begin(), toupper);
+    lang += "_" + lang_big;
+  }
+
+  return lang;
+
 }
 
 void
@@ -383,7 +453,7 @@ Dictionary::set_language(const LanguageDef& lang)
 }
 
 std::string
-Dictionary::translate(const std::string& msgid, const std::string& msgid2, int num) 
+Dictionary::translate(const std::string& msgid, const std::string& msgid2, int num)
 {
   PluralEntries::iterator i = plural_entries.find(msgid);
   std::map<int, std::string>& msgstrs = i->second;
@@ -428,7 +498,7 @@ Dictionary::translate(const char* msgid)
     }
   else
     {
-#ifdef TRANSLATION_DBEUG
+#ifdef TRANSLATION_DEBUG
       log_warning << "Couldn't translate: " << msgid << std::endl;
 #endif
       return msgid;
@@ -436,7 +506,7 @@ Dictionary::translate(const char* msgid)
 }
 
 std::string
-Dictionary::translate(const std::string& msgid) 
+Dictionary::translate(const std::string& msgid)
 {
   Entries::iterator i = entries.find(msgid);
   if (i != entries.end() && !i->second.empty())
@@ -445,13 +515,13 @@ Dictionary::translate(const std::string& msgid)
     }
   else
     {
-#ifdef TRANSLATION_DBEUG
+#ifdef TRANSLATION_DEBUG
       log_warning << "Couldn't translate: " << msgid << std::endl;
 #endif
       return msgid;
     }
 }
-  
+
 void
 Dictionary::add_translation(const std::string& msgid, const std::string& ,
                             const std::map<int, std::string>& msgstrs)
@@ -461,8 +531,8 @@ Dictionary::add_translation(const std::string& msgid, const std::string& ,
   plural_entries[msgid] = msgstrs;
 }
 
-void 
-Dictionary::add_translation(const std::string& msgid, const std::string& msgstr) 
+void
+Dictionary::add_translation(const std::string& msgid, const std::string& msgstr)
 {
   entries[msgid] = msgstr;
 }
@@ -470,47 +540,42 @@ Dictionary::add_translation(const std::string& msgid, const std::string& msgstr)
 class POFileReader
 {
 private:
-  struct Token
-  {
-    std::string keyword;
-    std::string content;
-  };
-
   Dictionary& dict;
+  std::istream& in;
 
   std::string from_charset;
   std::string to_charset;
 
-  std::string current_msgid;
-  std::string current_msgid_plural;
-  std::map<int, std::string> msgstr_plural;
-
   int line_num;
-
-  enum { WANT_MSGID, WANT_MSGSTR, WANT_MSGSTR_PLURAL, WANT_MSGID_PLURAL } state;
+  int c; //TODO: char c? unsigned char c?
+  enum Token {
+      TOKEN_KEYWORD, //msgstr, msgid, etc.
+      TOKEN_CONTENT, //string literals, concatenated ("" "foo\n" "bar\n" -> "foo\nbar\n")
+      TOKEN_EOF      //ran out of tokens
+  };
+  Token token;
+  std::string tokenContent; //current contents of the keyword or string literal(s)
 
 public:
-  POFileReader(std::istream& in, Dictionary& dict_)
-    : dict(dict_)
+  POFileReader(std::istream& in_, Dictionary& dict_)
+    : in(in_), dict(dict_)
   {
-    state = WANT_MSGID;
     line_num = 0;
-    char c = in.get();
-    if(c == (char) 0xef) { // skip UTF-8 intro that some texteditors produce
-        in.get();
-        in.get();
-    } else {
-        in.unget();
+    nextChar();
+    if(c == 0xef) { // skip UTF-8 intro that some text editors produce
+        nextChar();
+        nextChar();
+        nextChar();
     }
-    tokenize_po(in);
+    tokenize_po();
   }
 
   void parse_header(const std::string& header)
   {
-    // Seperate the header in lines
+    // Separate the header in lines
     typedef std::vector<std::string> Lines;
     Lines lines;
-    
+
     std::string::size_type start = 0;
     for(std::string::size_type i = 0; i < header.length(); ++i)
       {
@@ -542,179 +607,166 @@ public:
       }
   }
 
-  void add_token(const Token& token)
+  inline void nextChar()
   {
-    switch(state) 
-      {
-      case WANT_MSGID:
-        if (token.keyword == "msgid") 
-          {
-            current_msgid = token.content;
-            state = WANT_MSGID_PLURAL;
-          }
-        else if (token.keyword.empty())
-          {
-            //log_warning << "Got EOF, everything looks ok." << std::endl;
-          }
-        else
-          {
-            log_warning << "tinygettext: expected 'msgid' keyword, got " << token.keyword << " at line " << line_num << std::endl;
-          }
-        break;
-    
-      case WANT_MSGID_PLURAL:
-        if (token.keyword == "msgid_plural") 
-          {
-            current_msgid_plural = token.content;
-            state = WANT_MSGSTR_PLURAL;
-          } 
-        else
-          {
-            state = WANT_MSGSTR;
-            add_token(token);
-          }
-        break;
-
-      case WANT_MSGSTR:
-        if (token.keyword == "msgstr") 
-          {
-            if (current_msgid == "") 
-              { // .po Header is hidden in the msgid with the empty string
-                parse_header(token.content);
-              }
-            else
-              {
-                dict.add_translation(current_msgid, convert(token.content, from_charset, to_charset));
-              }
-            state = WANT_MSGID;
-          } 
-        else
-          {
-            log_warning << "tinygettext: expected 'msgstr' keyword, got " << token.keyword << " at line " << line_num << std::endl;
-          }
-        break;
+    c = in.get();
+    if (c == '\n')
+      line_num++;
+  }
 
-      case WANT_MSGSTR_PLURAL:
-        if (has_prefix(token.keyword, "msgstr[")) 
-          {
-            int num;
-            if (sscanf(token.keyword.c_str(), "msgstr[%d]", &num) != 1) 
-              {
-                log_warning << "Error: Couldn't parse: " << token.keyword << std::endl;
-              } 
-            else 
-              {
-                msgstr_plural[num] = convert(token.content, from_charset, to_charset);
-              }
-          }
-        else 
-          {
-            dict.add_translation(current_msgid, current_msgid_plural, msgstr_plural);
+  inline void skipSpace()
+  {
+    if(c == EOF)
+      return;
 
-            state = WANT_MSGID;
-            add_token(token);
-          }
-        break;
+    while(c == '#' || isspace(static_cast<unsigned char>(c))) {
+      if(c == '#') {
+        while(c != '\n' && c != EOF) nextChar();
       }
+      nextChar();
+    }
   }
-  
-  inline int getchar(std::istream& in) 
-  {
-    int c = in.get();
-    if (c == '\n')
-      line_num += 1;
-    return c;
+
+  inline bool expectToken(std::string type, Token wanted) {
+     if(token != wanted) {
+        log_warning << "Expected " << type << ", got ";
+        if(token == TOKEN_EOF)
+          log_warning << "EOF";
+        else if(token == TOKEN_KEYWORD)
+          log_warning << "keyword '" << tokenContent << "'";
+        else
+          log_warning << "string \"" << tokenContent << '"';
+
+        log_warning << " at line " << line_num << std::endl;
+        return false;
+     }
+     return true;
   }
-  
-  void tokenize_po(std::istream& in)
-  {
-    enum State { READ_KEYWORD, 
-                 READ_CONTENT,
-                 READ_CONTENT_IN_STRING,
-                 SKIP_COMMENT };
 
-    State state = READ_KEYWORD;
-    int c;
-    Token token;
+  inline bool expectContent(std::string type, std::string wanted) {
+     if(tokenContent != wanted) {
+        log_warning << "Expected " << type << ", got ";
+        if(token == TOKEN_EOF)
+          log_warning << "EOF";
+        else if(token == TOKEN_KEYWORD)
+          log_warning << "keyword '" << tokenContent << "'";
+        else
+          log_warning << "string \"" << tokenContent << '"';
 
-    while((c = getchar(in)) != EOF)
-      {
-        //log_debug << "Lexing char: " << char(c) << " " << state << std::endl;
-        switch(state)
-          {
-          case READ_KEYWORD:
-            if (c == '#')
-              {
-                state = SKIP_COMMENT;
-              }
-            else
-              {
-                // Read a new token
-                token = Token();
-                
-                do { // Read keyword 
-                  token.keyword += c;
-                } while((c = getchar(in)) != EOF && !isspace(c));
-                in.unget();
-
-                state = READ_CONTENT;
-              }
-            break;
-
-          case READ_CONTENT:
-            while((c = getchar(in)) != EOF)
-              {
-                if (c == '"') { 
-                  // Found start of content
-                  state = READ_CONTENT_IN_STRING;
-                  break;
-                } else if (isspace(c)) {
-                  // skip
-                } else { // Read something that may be a keyword
-                  in.unget();
-                  state = READ_KEYWORD;
-                  add_token(token);
-                  break;
-                }
-              }
-            break;
+        log_warning << " at line " << line_num << std::endl;
+        return false;
+     }
+     return true;
+  }
 
-          case READ_CONTENT_IN_STRING:
-            if (c == '\\') {
-              c = getchar(in);
-              if (c != EOF)
+  void tokenize_po()
+    {
+      while((token = nextToken()) != TOKEN_EOF)
+        {
+          if(!expectToken("'msgid' keyword", TOKEN_KEYWORD) || !expectContent("'msgid' keyword", "msgid")) break;
+
+          token = nextToken();
+          if(!expectToken("name after msgid", TOKEN_CONTENT)) break;
+          std::string current_msgid = tokenContent;
+
+          token = nextToken();
+          if(!expectToken("msgstr or msgid_plural", TOKEN_KEYWORD)) break;
+          if(tokenContent == "msgid_plural")
+            {
+              //Plural form
+              token = nextToken();
+              if(!expectToken("msgid_plural content", TOKEN_CONTENT)) break;
+              std::string current_msgid_plural = tokenContent;
+
+              std::map<int, std::string> msgstr_plural;
+              while((token = nextToken()) == TOKEN_KEYWORD && has_prefix(tokenContent, "msgstr["))
                 {
-                  if (c == 'n') token.content += '\n';
-                  else if (c == 't') token.content += '\t';
-                  else if (c == 'r') token.content += '\r';
-                  else if (c == '"') token.content += '"';
-                  else
+                  int num;
+                  if (sscanf(tokenContent.c_str(), "msgstr[%d]", &num) != 1)
                     {
-                      log_warning << "Unhandled escape character: " << char(c) << std::endl;
+                      log_warning << "Error: Couldn't parse: " << tokenContent << std::endl;
                     }
+
+                  token = nextToken();
+                  if(!expectToken("msgstr[x] content", TOKEN_CONTENT)) break;
+                  msgstr_plural[num] = convert(tokenContent, from_charset, to_charset);
+                }
+              dict.add_translation(current_msgid, current_msgid_plural, msgstr_plural);
+            }
+          else
+            {
+              // "Ordinary" translation
+              if(!expectContent("'msgstr' keyword", "msgstr")) break;
+
+              token = nextToken();
+              if(!expectToken("translation in msgstr", TOKEN_CONTENT)) break;
+
+              if (current_msgid == "")
+                { // .po Header is hidden in the msgid with the empty string
+                  parse_header(tokenContent);
                 }
               else
                 {
-                  log_warning << "Unterminated string" << std::endl;
+                  dict.add_translation(current_msgid, convert(tokenContent, from_charset, to_charset));
                 }
-            } else if (c == '"') { // Content string is terminated
-              state = READ_CONTENT;
-            } else {
-              token.content += c;
             }
-            break;
+        }
+    }
+
+  Token nextToken()
+  {
+    //Clear token contents
+    tokenContent = "";
 
-          case SKIP_COMMENT:
-            if (c == '\n')
-              state = READ_KEYWORD;
-            break;
+    skipSpace();
+
+    if(c == EOF)
+      return TOKEN_EOF;
+    else if(c != '"')
+      {
+        // Read a keyword
+        do {
+          tokenContent += c;
+          nextChar();
+        } while(c != EOF && !isspace(static_cast<unsigned char>(c)));
+        return TOKEN_KEYWORD;
+      }
+    else
+      {
+        do {
+          nextChar();
+          // Read content
+          while(c != EOF && c != '"') {
+            if (c == '\\') {
+              nextChar();
+              if (c == 'n') c = '\n';
+              else if (c == 't') c = '\t';
+              else if (c == 'r') c = '\r';
+              else if (c == '"') c = '"';
+              else if (c == '\\') c = '\\';
+              else
+                {
+                  log_warning << "Unhandled escape character: " << char(c) << std::endl;
+                  c = ' ';
+                }
+            }
+            tokenContent += c;
+            nextChar();
+          }
+          if(c == EOF) {
+            log_warning << "Unclosed string literal: " << tokenContent << std::endl;
+            return TOKEN_CONTENT;
           }
+
+          // Read more strings?
+          skipSpace();
+        } while(c == '"');
+        return TOKEN_CONTENT;
       }
-    add_token(token);
   }
 };
 
-void read_po_file(Dictionary& dict_, std::istream& in) 
+void read_po_file(Dictionary& dict_, std::istream& in)
 {
   POFileReader reader(in, dict_);
 }