5a5f322969c7058104043642d3652089191f6ba9
[vuplus_xbmc] / xbmc / guilib / LocalizeStrings.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "system.h"
22 #include "LocalizeStrings.h"
23 #include "utils/CharsetConverter.h"
24 #include "utils/log.h"
25 #include "filesystem/SpecialProtocol.h"
26 #include "utils/XMLUtils.h"
27 #include "utils/URIUtils.h"
28 #include "utils/POUtils.h"
29 #include "filesystem/Directory.h"
30 #include "threads/SingleLock.h"
31
32 CLocalizeStrings::CLocalizeStrings(void)
33 {
34
35 }
36
37 CLocalizeStrings::~CLocalizeStrings(void)
38 {
39
40 }
41
42 CStdString CLocalizeStrings::ToUTF8(const CStdString& strEncoding, const CStdString& str)
43 {
44   if (strEncoding.IsEmpty())
45     return str;
46
47   CStdString ret;
48   g_charsetConverter.ToUtf8(strEncoding, str, ret);
49   return ret;
50 }
51
52 void CLocalizeStrings::ClearSkinStrings()
53 {
54   // clear the skin strings
55   Clear(31000, 31999);
56 }
57
58 bool CLocalizeStrings::LoadSkinStrings(const CStdString& path, const CStdString& language)
59 {
60   ClearSkinStrings();
61   // load the skin strings in.
62   CStdString encoding;
63   if (!LoadStr2Mem(path, language, encoding))
64   {
65     if (language.Equals(SOURCE_LANGUAGE)) // no fallback, nothing to do
66       return false;
67   }
68
69   // load the fallback
70   if (!language.Equals(SOURCE_LANGUAGE))
71     LoadStr2Mem(path, SOURCE_LANGUAGE, encoding);
72
73   return true;
74 }
75
76 bool CLocalizeStrings::LoadStr2Mem(const CStdString &pathname_in, const CStdString &language,
77                                    CStdString &encoding, uint32_t offset /* = 0 */)
78 {
79   CStdString pathname = CSpecialProtocol::TranslatePathConvertCase(pathname_in + language);
80   if (!XFILE::CDirectory::Exists(pathname))
81   {
82     CLog::Log(LOGDEBUG,
83               "LocalizeStrings: no translation available in currently set gui language, at path %s",
84               pathname.c_str());
85     return false;
86   }
87
88   if (LoadPO(URIUtils::AddFileToFolder(pathname, "strings.po"), encoding, offset,
89       language.Equals(SOURCE_LANGUAGE)))
90     return true;
91
92   CLog::Log(LOGDEBUG, "LocalizeStrings: no strings.po file exist at %s, fallback to strings.xml",
93             pathname.c_str());
94   return LoadXML(URIUtils::AddFileToFolder(pathname, "strings.xml"), encoding, offset);
95 }
96
97 bool CLocalizeStrings::LoadPO(const CStdString &filename, CStdString &encoding,
98                               uint32_t offset /* = 0 */, bool bSourceLanguage)
99 {
100   CPODocument PODoc;
101   if (!PODoc.LoadFile(filename))
102     return false;
103
104   int counter = 0;
105
106   while ((PODoc.GetNextEntry()))
107   {
108     uint32_t id;
109     if (PODoc.GetEntryType() == ID_FOUND)
110     {
111       bool bStrInMem = m_strings.find((id = PODoc.GetEntryID()) + offset) != m_strings.end();
112       PODoc.ParseEntry(bSourceLanguage);
113
114       if (bSourceLanguage && !PODoc.GetMsgid().empty())
115       {
116         if (bStrInMem && (m_strings[id + offset].strOriginal.IsEmpty() ||
117             PODoc.GetMsgid() == m_strings[id + offset].strOriginal))
118           continue;
119         else if (bStrInMem)
120           CLog::Log(LOGDEBUG,
121                     "POParser: id:%i was recently re-used in the English string file, which is not yet "
122                     "changed in the translated file. Using the English string instead", id);
123         m_strings[id + offset].strTranslated = PODoc.GetMsgid();
124         counter++;
125       }
126       else if (!bSourceLanguage && !bStrInMem && !PODoc.GetMsgstr().empty())
127       {
128         m_strings[id + offset].strTranslated = PODoc.GetMsgstr();
129         m_strings[id + offset].strOriginal = PODoc.GetMsgid();
130         counter++;
131       }
132     }
133     else if (PODoc.GetEntryType() == MSGID_FOUND)
134     {
135       // TODO: implement reading of non-id based string entries from the PO files.
136       // These entries would go into a separate memory map, using hash codes for fast look-up.
137       // With this memory map we can implement using gettext(), ngettext(), pgettext() calls,
138       // so that we don't have to use new IDs for new strings. Even we can start converting
139       // the ID based calls to normal gettext calls.
140     }
141     else if (PODoc.GetEntryType() == MSGID_PLURAL_FOUND)
142     {
143       // TODO: implement reading of non-id based pluralized string entries from the PO files.
144       // We can store the pluralforms for each language, in the langinfo.xml files.
145     }
146   }
147
148   CLog::Log(LOGDEBUG, "POParser: loaded %i strings from file %s", counter, filename.c_str());
149   return true;
150 }
151
152 bool CLocalizeStrings::LoadXML(const CStdString &filename, CStdString &encoding, uint32_t offset /* = 0 */)
153 {
154   CXBMCTinyXML xmlDoc;
155   if (!xmlDoc.LoadFile(filename))
156   {
157     CLog::Log(LOGDEBUG, "unable to load %s: %s at line %d", filename.c_str(), xmlDoc.ErrorDesc(), xmlDoc.ErrorRow());
158     return false;
159   }
160
161   XMLUtils::GetEncoding(&xmlDoc, encoding);
162
163   TiXmlElement* pRootElement = xmlDoc.RootElement();
164   if (!pRootElement || pRootElement->NoChildren() ||
165        pRootElement->ValueStr()!=CStdString("strings"))
166   {
167     CLog::Log(LOGERROR, "%s Doesn't contain <strings>", filename.c_str());
168     return false;
169   }
170
171   const TiXmlElement *pChild = pRootElement->FirstChildElement("string");
172   while (pChild)
173   {
174     // Load old style language file with id as attribute
175     const char* attrId=pChild->Attribute("id");
176     if (attrId && !pChild->NoChildren())
177     {
178       int id = atoi(attrId) + offset;
179       if (m_strings.find(id) == m_strings.end())
180         m_strings[id].strTranslated = ToUTF8(encoding, pChild->FirstChild()->Value());
181     }
182     pChild = pChild->NextSiblingElement("string");
183   }
184   return true;
185 }
186
187 bool CLocalizeStrings::Load(const CStdString& strPathName, const CStdString& strLanguage)
188 {
189   bool bLoadFallback = !strLanguage.Equals(SOURCE_LANGUAGE);
190
191   CStdString encoding;
192   CSingleLock lock(m_critSection);
193   Clear();
194
195   if (!LoadStr2Mem(strPathName, strLanguage, encoding))
196   {
197     // try loading the fallback
198     if (!bLoadFallback || !LoadStr2Mem(strPathName, SOURCE_LANGUAGE, encoding))
199       return false;
200
201     bLoadFallback = false;
202   }
203
204   if (bLoadFallback)
205     LoadStr2Mem(strPathName, SOURCE_LANGUAGE, encoding);
206
207   // fill in the constant strings
208   m_strings[20022].strTranslated = "";
209   m_strings[20027].strTranslated = "°F";
210   m_strings[20028].strTranslated = "K";
211   m_strings[20029].strTranslated = "°C";
212   m_strings[20030].strTranslated = "°Ré";
213   m_strings[20031].strTranslated = "°Ra";
214   m_strings[20032].strTranslated = "°Rø";
215   m_strings[20033].strTranslated = "°De";
216   m_strings[20034].strTranslated = "°N";
217
218   m_strings[20200].strTranslated = "km/h";
219   m_strings[20201].strTranslated = "m/min";
220   m_strings[20202].strTranslated = "m/s";
221   m_strings[20203].strTranslated = "ft/h";
222   m_strings[20204].strTranslated = "ft/min";
223   m_strings[20205].strTranslated = "ft/s";
224   m_strings[20206].strTranslated = "mph";
225   m_strings[20207].strTranslated = "kts";
226   m_strings[20208].strTranslated = "Beaufort";
227   m_strings[20209].strTranslated = "inch/s";
228   m_strings[20210].strTranslated = "yard/s";
229   m_strings[20211].strTranslated = "Furlong/Fortnight";
230
231   return true;
232 }
233
234 static CStdString szEmptyString = "";
235
236 const CStdString& CLocalizeStrings::Get(uint32_t dwCode) const
237 {
238   ciStrings i = m_strings.find(dwCode);
239   if (i == m_strings.end())
240   {
241     return szEmptyString;
242   }
243   return i->second.strTranslated;
244 }
245
246 void CLocalizeStrings::Clear()
247 {
248   m_strings.clear();
249 }
250
251 void CLocalizeStrings::Clear(uint32_t start, uint32_t end)
252 {
253   iStrings it = m_strings.begin();
254   while (it != m_strings.end())
255   {
256     if (it->first >= start && it->first <= end)
257       m_strings.erase(it++);
258     else
259       ++it;
260   }
261 }
262
263 uint32_t CLocalizeStrings::LoadBlock(const CStdString &id, const CStdString &path, const CStdString &language)
264 {
265   iBlocks it = m_blocks.find(id);
266   if (it != m_blocks.end())
267     return it->second;  // already loaded
268
269   // grab a new block
270   uint32_t offset = block_start + m_blocks.size()*block_size;
271   m_blocks.insert(make_pair(id, offset));
272
273   // load the strings
274   CStdString encoding;
275   bool success = LoadStr2Mem(path, language, encoding, offset);
276   if (!success)
277   {
278     if (language.Equals(SOURCE_LANGUAGE)) // no fallback, nothing to do
279       return 0;
280   }
281
282   // load the fallback
283   if (!language.Equals(SOURCE_LANGUAGE))
284     success |= LoadStr2Mem(path, SOURCE_LANGUAGE, encoding, offset);
285
286   return success ? offset : 0;
287 }
288
289 void CLocalizeStrings::ClearBlock(const CStdString &id)
290 {
291   iBlocks it = m_blocks.find(id);
292   if (it == m_blocks.end())
293   {
294     CLog::Log(LOGERROR, "%s: Trying to clear non existent block %s", __FUNCTION__, id.c_str());
295     return; // doesn't exist
296   }
297
298   // clear our block
299   Clear(it->second, it->second + block_size);
300   m_blocks.erase(it);
301 }