2 * Copyright (C) 2005-2013 Team XBMC
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)
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.
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/>.
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"
32 CLocalizeStrings::CLocalizeStrings(void)
37 CLocalizeStrings::~CLocalizeStrings(void)
42 CStdString CLocalizeStrings::ToUTF8(const CStdString& strEncoding, const CStdString& str)
44 if (strEncoding.IsEmpty())
48 g_charsetConverter.ToUtf8(strEncoding, str, ret);
52 void CLocalizeStrings::ClearSkinStrings()
54 // clear the skin strings
58 bool CLocalizeStrings::LoadSkinStrings(const CStdString& path, const CStdString& language)
61 // load the skin strings in.
63 if (!LoadStr2Mem(path, language, encoding))
65 if (language.Equals(SOURCE_LANGUAGE)) // no fallback, nothing to do
70 if (!language.Equals(SOURCE_LANGUAGE))
71 LoadStr2Mem(path, SOURCE_LANGUAGE, encoding);
76 bool CLocalizeStrings::LoadStr2Mem(const CStdString &pathname_in, const CStdString &language,
77 CStdString &encoding, uint32_t offset /* = 0 */)
79 CStdString pathname = CSpecialProtocol::TranslatePathConvertCase(pathname_in + language);
80 if (!XFILE::CDirectory::Exists(pathname))
83 "LocalizeStrings: no translation available in currently set gui language, at path %s",
88 if (LoadPO(URIUtils::AddFileToFolder(pathname, "strings.po"), encoding, offset,
89 language.Equals(SOURCE_LANGUAGE)))
92 CLog::Log(LOGDEBUG, "LocalizeStrings: no strings.po file exist at %s, fallback to strings.xml",
94 return LoadXML(URIUtils::AddFileToFolder(pathname, "strings.xml"), encoding, offset);
97 bool CLocalizeStrings::LoadPO(const CStdString &filename, CStdString &encoding,
98 uint32_t offset /* = 0 */, bool bSourceLanguage)
101 if (!PODoc.LoadFile(filename))
106 while ((PODoc.GetNextEntry()))
109 if (PODoc.GetEntryType() == ID_FOUND)
111 bool bStrInMem = m_strings.find((id = PODoc.GetEntryID()) + offset) != m_strings.end();
112 PODoc.ParseEntry(bSourceLanguage);
114 if (bSourceLanguage && !PODoc.GetMsgid().empty())
116 if (bStrInMem && (m_strings[id + offset].strOriginal.IsEmpty() ||
117 PODoc.GetMsgid() == m_strings[id + offset].strOriginal))
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();
126 else if (!bSourceLanguage && !bStrInMem && !PODoc.GetMsgstr().empty())
128 m_strings[id + offset].strTranslated = PODoc.GetMsgstr();
129 m_strings[id + offset].strOriginal = PODoc.GetMsgid();
133 else if (PODoc.GetEntryType() == MSGID_FOUND)
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.
141 else if (PODoc.GetEntryType() == MSGID_PLURAL_FOUND)
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.
148 CLog::Log(LOGDEBUG, "POParser: loaded %i strings from file %s", counter, filename.c_str());
152 bool CLocalizeStrings::LoadXML(const CStdString &filename, CStdString &encoding, uint32_t offset /* = 0 */)
155 if (!xmlDoc.LoadFile(filename))
157 CLog::Log(LOGDEBUG, "unable to load %s: %s at line %d", filename.c_str(), xmlDoc.ErrorDesc(), xmlDoc.ErrorRow());
161 XMLUtils::GetEncoding(&xmlDoc, encoding);
163 TiXmlElement* pRootElement = xmlDoc.RootElement();
164 if (!pRootElement || pRootElement->NoChildren() ||
165 pRootElement->ValueStr()!=CStdString("strings"))
167 CLog::Log(LOGERROR, "%s Doesn't contain <strings>", filename.c_str());
171 const TiXmlElement *pChild = pRootElement->FirstChildElement("string");
174 // Load old style language file with id as attribute
175 const char* attrId=pChild->Attribute("id");
176 if (attrId && !pChild->NoChildren())
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());
182 pChild = pChild->NextSiblingElement("string");
187 bool CLocalizeStrings::Load(const CStdString& strPathName, const CStdString& strLanguage)
189 bool bLoadFallback = !strLanguage.Equals(SOURCE_LANGUAGE);
192 CSingleLock lock(m_critSection);
195 if (!LoadStr2Mem(strPathName, strLanguage, encoding))
197 // try loading the fallback
198 if (!bLoadFallback || !LoadStr2Mem(strPathName, SOURCE_LANGUAGE, encoding))
201 bLoadFallback = false;
205 LoadStr2Mem(strPathName, SOURCE_LANGUAGE, encoding);
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";
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";
234 static CStdString szEmptyString = "";
236 const CStdString& CLocalizeStrings::Get(uint32_t dwCode) const
238 ciStrings i = m_strings.find(dwCode);
239 if (i == m_strings.end())
241 return szEmptyString;
243 return i->second.strTranslated;
246 void CLocalizeStrings::Clear()
251 void CLocalizeStrings::Clear(uint32_t start, uint32_t end)
253 iStrings it = m_strings.begin();
254 while (it != m_strings.end())
256 if (it->first >= start && it->first <= end)
257 m_strings.erase(it++);
263 uint32_t CLocalizeStrings::LoadBlock(const CStdString &id, const CStdString &path, const CStdString &language)
265 iBlocks it = m_blocks.find(id);
266 if (it != m_blocks.end())
267 return it->second; // already loaded
270 uint32_t offset = block_start + m_blocks.size()*block_size;
271 m_blocks.insert(make_pair(id, offset));
275 bool success = LoadStr2Mem(path, language, encoding, offset);
278 if (language.Equals(SOURCE_LANGUAGE)) // no fallback, nothing to do
283 if (!language.Equals(SOURCE_LANGUAGE))
284 success |= LoadStr2Mem(path, SOURCE_LANGUAGE, encoding, offset);
286 return success ? offset : 0;
289 void CLocalizeStrings::ClearBlock(const CStdString &id)
291 iBlocks it = m_blocks.find(id);
292 if (it == m_blocks.end())
294 CLog::Log(LOGERROR, "%s: Trying to clear non existent block %s", __FUNCTION__, id.c_str());
295 return; // doesn't exist
299 Clear(it->second, it->second + block_size);