1 /***************************************************************************
2  *      Mechanized Assault and Exploration Reloaded Projectfile            *
3  *                                                                         *
4  *   This program is free software; you can redistribute it and/or modify  *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This program is distributed in the hope that it will be useful,       *
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12  *   GNU General Public License for more details.                          *
13  *                                                                         *
14  *   You should have received a copy of the GNU General Public License     *
15  *   along with this program; if not, write to the                         *
16  *   Free Software Foundation, Inc.,                                       *
17  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
18  ***************************************************************************/
19 ////////////////////////////////////////////////////////////////////////////////
20 //
21 //  File:   language.cpp
22 //  Date:   07-10-01
23 //  Author: JCK
24 //
25 ////////////////////////////////////////////////////////////////////////////////
26 //  Description:
27 //  This class handles the support for language packs in XML-format.
28 ////////////////////////////////////////////////////////////////////////////////
29 
30 #include "utility/language.h"
31 #include "maxrversion.h"
32 #include "extendedtinyxml.h"
33 #include "utility/log.h"
34 #include "main.h"
35 #include "settings.h"
36 #include "utility/files.h"
37 #include "tinyxml2.h"
38 #include "utility/string/toupper.h"
39 
40 using namespace tinyxml2;
41 
cLanguage()42 cLanguage::cLanguage() :
43 	m_bLeftToRight (true),
44 	m_bErrorMsgTranslationLoaded (false)
45 {}
46 
GetCurrentLanguage() const47 const std::string& cLanguage::GetCurrentLanguage() const
48 {
49 	return m_szLanguage;
50 }
51 
SetCurrentLanguage(const std::string & szLanguageCode)52 int cLanguage::SetCurrentLanguage (const std::string& szLanguageCode)
53 {
54 	// don't do this in constructor
55 	// because language folder isn't known yet in programm start.
56 	// since the first thing we do with language files is setting
57 	// our language we can init the master lang file here too -- beko
58 	m_szLanguageFileMaster = LANGUAGE_FILE_FOLDER;
59 	m_szLanguageFileMaster += PATH_DELIMITER LANGUAGE_FILE_NAME "eng" LANGUAGE_FILE_EXT;
60 
61 	if (szLanguageCode.length() != 3)
62 	{
63 		return -1;
64 	}
65 	if (szLanguageCode.find_first_not_of
66 		("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") != std::string::npos)
67 	{
68 		return -1;
69 	}
70 
71 	m_szLanguage = szLanguageCode;
72 	for (int i = 0; i <= 2; i++)
73 	{
74 		if (m_szLanguage[i] < 'a')
75 		{
76 			m_szLanguage[i] += 'a' - 'A';
77 		}
78 	}
79 
80 	m_szLanguageFile = LANGUAGE_FILE_FOLDER;
81 	m_szLanguageFile += PATH_DELIMITER LANGUAGE_FILE_NAME;
82 	m_szLanguageFile += m_szLanguage + LANGUAGE_FILE_EXT;
83 	return 0;
84 }
85 
i18n(const std::string & szInputText)86 std::string cLanguage::i18n (const std::string& szInputText)
87 {
88 	StrStrMap::const_iterator impTranslation;
89 	impTranslation = m_mpLanguage.find (szInputText);
90 
91 	if (impTranslation == m_mpLanguage.end())
92 	{
93 		if (m_bErrorMsgTranslationLoaded)
94 		{
95 			return i18n ("Text~Error_Messages~ERROR_Missing_Translation", szInputText);
96 		}
97 		else
98 		{
99 			return std::string ("missing translation: ") + szInputText;
100 		}
101 	}
102 	else
103 	{
104 		return impTranslation->second;
105 	}
106 }
107 
108 // Translation with replace %s
i18n(const std::string & szMainText,const std::string & szInsertText)109 std::string cLanguage::i18n (const std::string& szMainText, const std::string& szInsertText)
110 {
111 	std::string szMainTextNew;
112 	std::size_t iPos;
113 
114 	szMainTextNew = this->i18n (szMainText);
115 	iPos = szMainTextNew.find ("%s");
116 	if (iPos == std::string::npos)
117 	{
118 		Log.write ("Found no place holder in language string. Update language file!", cLog::eLOG_TYPE_WARNING);
119 		Log.write ("*-> String in question is: \"" + szMainText + "\"", cLog::eLOG_TYPE_WARNING);
120 		return szMainTextNew + szInsertText;
121 	}
122 	else
123 	{
124 		szMainTextNew.replace (iPos, 2, szInsertText);
125 		return szMainTextNew;
126 	}
127 }
128 
ReadLanguagePack()129 int cLanguage::ReadLanguagePack()
130 {
131 	// First let's load the English language pack and use it as master
132 	if (ReadLanguagePackFooter() != 0)
133 	{
134 		return -1;
135 	}
136 
137 	// Read the complete <Text> - section
138 	XMLElement* xmlStartingElement = nullptr;
139 	try
140 	{
141 		xmlStartingElement = XmlGetFirstElement (m_XmlDoc, XNP_MAX_LANG_FILE_TEXT);
142 		if (xmlStartingElement == nullptr) throw std::string ("No <Text> section found!");
143 
144 		xmlStartingElement = xmlStartingElement->FirstChildElement();
145 		if (xmlStartingElement == nullptr) throw std::string ("<Text> section is empty!");
146 	}
147 	catch (const std::string& strMsg)
148 	{
149 		Log.write ("Language file (eng): " + strMsg, cLog::eLOG_TYPE_ERROR);
150 		return -1;
151 	}
152 	ReadRecursiveLanguagePack (xmlStartingElement, "Text");
153 
154 	// Read the complete <Graphic> - section
155 	xmlStartingElement = nullptr;
156 	try
157 	{
158 		xmlStartingElement = XmlGetFirstElement (m_XmlDoc, XNP_MAX_LANG_FILE_GRAPHIC);
159 		if (xmlStartingElement == nullptr) throw std::string ("No <Graphic> section found!");
160 
161 		xmlStartingElement = xmlStartingElement->FirstChildElement();
162 		if (xmlStartingElement == nullptr) throw std::string ("<Graphic> section is empty!");
163 	}
164 	catch (const std::string& strMsg)
165 	{
166 		Log.write ("Language file (eng): " + strMsg, cLog::eLOG_TYPE_ERROR);
167 		return -1;
168 	}
169 	ReadRecursiveLanguagePack (xmlStartingElement, "Graphic");
170 
171 	// Read the complete <Speech> - section
172 	xmlStartingElement = nullptr;
173 	try
174 	{
175 		xmlStartingElement = XmlGetFirstElement (m_XmlDoc, XNP_MAX_LANG_FILE_SPEECH);
176 		if (xmlStartingElement == nullptr) throw std::string ("No <Speech> section found!");
177 
178 		xmlStartingElement = xmlStartingElement->FirstChildElement();
179 		if (xmlStartingElement == nullptr) throw std::string ("<Speech> section is empty!");
180 	}
181 	catch (const std::string& strMsg)
182 	{
183 		Log.write ("Language file (eng): " + strMsg, cLog::eLOG_TYPE_ERROR);
184 		return -1;
185 	}
186 	ReadRecursiveLanguagePack (xmlStartingElement, "Speech");
187 
188 	// Second step: Load the selected language pack
189 	if (m_szLanguage == "eng")
190 	{
191 		StrStrMap::const_iterator impTranslation;
192 		impTranslation = m_mpLanguage.find ("Text~Error_Messages~ERROR_Missing_Translation");
193 		if (impTranslation == m_mpLanguage.end())
194 		{
195 			m_mpLanguage["Text~Error_Messages~ERROR_Missing_Translation"] = "missing translation: %s";
196 		}
197 		m_bErrorMsgTranslationLoaded = true;
198 		return 0;
199 	}
200 	if (ReadLanguagePackFooter (m_szLanguage) != 0)
201 	{
202 		return -1;
203 	}
204 
205 	// Now - finally - let's get the translations.
206 	if (ReadSingleTranslation (XNP_MAX_LANG_FILE_TEXT_ERROR_MSG, "ERROR_Missing_Translation", nullptr) == 0)
207 	{
208 		m_bErrorMsgTranslationLoaded = true;
209 	}
210 
211 	for (StrStrMap::const_iterator impPosition = m_mpLanguage.begin();
212 		 impPosition !=  m_mpLanguage.end();
213 		 ++impPosition)
214 	{
215 		std::string strResult = ReadSingleTranslation (impPosition->first);
216 		if (strResult != "")
217 		{
218 			m_mpLanguage[impPosition->first] = strResult;
219 		}
220 		else
221 		{
222 			// TODO: ?
223 		}
224 	}
225 
226 	Log.write (this->i18n ("Text~Error_Messages~INFO_Language_initialised"), cLog::eLOG_TYPE_INFO);
227 
228 	return 0;
229 }
230 
CheckCurrentLanguagePack(bool bInsertMissingEntries)231 int cLanguage::CheckCurrentLanguagePack (bool bInsertMissingEntries)
232 {
233 	//TODO: - JCK: Check and correct a language pack
234 	return 0;
235 }
236 
getAvailableLanguages() const237 std::vector<std::string> cLanguage::getAvailableLanguages() const
238 {
239 	std::vector<std::string> languageCodes;
240 
241 	const auto fileNames = getFilesOfDirectory (LANGUAGE_FILE_FOLDER);
242 
243 	const auto languageFileNameSize = strlen (LANGUAGE_FILE_NAME);
244 	const auto languageFileExtensionSize = strlen (LANGUAGE_FILE_EXT);
245 
246 	for (size_t i = 0; i < fileNames.size(); ++i)
247 	{
248 		const auto& fileName = fileNames[i];
249 
250 		if (fileName.size() <= languageFileNameSize + languageFileExtensionSize) continue;
251 
252 		const auto prefix = fileName.substr (0, languageFileNameSize);
253 
254 		if (prefix.compare (LANGUAGE_FILE_NAME) != 0) continue;
255 
256 		const auto suffix = fileName.substr (fileName.size() - languageFileExtensionSize);
257 
258 		if (suffix.compare (LANGUAGE_FILE_EXT) != 0) continue;
259 
260 		const auto languageCode = fileName.substr (languageFileNameSize, fileName.size() - languageFileExtensionSize - languageFileNameSize);
261 
262 		languageCodes.push_back (to_upper_copy (languageCode));
263 	}
264 
265 	return languageCodes;
266 }
267 
ReadSingleTranslation(const char * pszCurrent,...)268 int cLanguage::ReadSingleTranslation (const char* pszCurrent, ...)
269 {
270 	va_list pvaArg;
271 	va_start (pvaArg, pszCurrent);
272 	StrStrMap::const_iterator impTranslation;
273 	std::string szErrorMsg;
274 
275 	XMLElement* xmlElement = nullptr;
276 	std::string szXmlNodePath;
277 
278 	for (;;)
279 	{
280 		xmlElement = m_XmlDoc.RootElement();
281 		if (xmlElement == nullptr)
282 		{
283 			break;
284 		}
285 
286 		if (strcmp (xmlElement->Value(), pszCurrent) != 0)
287 		{
288 			break;
289 		}
290 
291 		do
292 		{
293 			pszCurrent = va_arg (pvaArg, char*);
294 			if (pszCurrent != nullptr)
295 			{
296 				szXmlNodePath += "~";
297 				szXmlNodePath += pszCurrent;
298 				xmlElement = xmlElement->FirstChildElement (pszCurrent);
299 				if (xmlElement == nullptr)
300 				{
301 					break;
302 				}
303 			}
304 		}
305 		while (pszCurrent != nullptr);
306 		break;
307 	}
308 	szXmlNodePath.erase (0, 1);
309 
310 	if (xmlElement != nullptr)
311 	{
312 		const char* value = xmlElement->Attribute ("localized");
313 		if (value)
314 		{
315 			va_end (pvaArg);
316 			impTranslation = m_mpLanguage.find (szXmlNodePath);
317 			if (impTranslation == m_mpLanguage.end())
318 			{
319 				szErrorMsg = "Language file: translation for >";
320 				szErrorMsg += szXmlNodePath + "< is read more than once!";
321 				Log.write (szErrorMsg, cLog::eLOG_TYPE_WARNING);
322 				return -1;
323 			}
324 			m_mpLanguage[szXmlNodePath] = value;
325 			return 0;
326 		}
327 	}
328 
329 	szErrorMsg = "Language file: translation for >";
330 	if (xmlElement != nullptr)
331 	{
332 		const char* value = xmlElement->Attribute ("ENG");
333 		if (value != nullptr)
334 		{
335 			m_mpLanguage[szXmlNodePath] = value;
336 			szErrorMsg += std::string (value) + "< is missing";
337 		}
338 		else
339 		{
340 			if (m_bErrorMsgTranslationLoaded)
341 			{
342 				m_mpLanguage[szXmlNodePath] = i18n ("Text~Error_Messages~ERROR_Missing_Translation") + szXmlNodePath;
343 			}
344 			else
345 			{
346 				m_mpLanguage[szXmlNodePath] = std::string ("missing translation: ") + szXmlNodePath;
347 			}
348 			szErrorMsg += szXmlNodePath + "< is missing";
349 		}
350 	}
351 	else
352 	{
353 		if (m_bErrorMsgTranslationLoaded)
354 		{
355 			m_mpLanguage[szXmlNodePath] = i18n ("Text~Error_Messages~ERROR_Missing_Translation", szXmlNodePath);
356 		}
357 		else
358 		{
359 			m_mpLanguage[szXmlNodePath] = std::string ("missing translation: ") + szXmlNodePath;
360 		}
361 		szErrorMsg += szXmlNodePath + "< is missing";
362 	}
363 	Log.write (szErrorMsg, cLog::eLOG_TYPE_WARNING);
364 	va_end (pvaArg);
365 	return -1;
366 }
367 
368 /////////////
369 //ExTiXmlNode* node;
370 //node = XmlGetFirstNode (...);
371 //while (node != nullptr)
372 //{
373 //	data = node->XmlReadNodeData (...);
374 //	node = node-XmlGetFirstNodeChild();
375 //child = child->NextSibling() )
376 /////////////
377 
ReadLanguagePackFooter()378 int cLanguage::ReadLanguagePackFooter()
379 {
380 	return ReadLanguagePackFooter ("");
381 }
382 
ReadLanguagePackFooter(const std::string & strLanguageCode)383 int cLanguage::ReadLanguagePackFooter (const std::string& strLanguageCode)
384 {
385 	XMLElement* xmlElement = nullptr;
386 	std::string strErrorMsg;
387 	std::string strFileName;
388 	std::string szLanguageCode (strLanguageCode);
389 
390 	if (szLanguageCode.empty())
391 	{
392 		strFileName = m_szLanguageFileMaster;
393 		szLanguageCode = "eng";
394 	}
395 	else
396 	{
397 		strFileName = m_szLanguageFile;
398 	}
399 
400 	// Load the file
401 	if (m_XmlDoc.LoadFile (strFileName.c_str()) != XML_NO_ERROR)
402 	{
403 		strErrorMsg = "Can't open language file :" + strFileName;
404 		Log.write (strErrorMsg, cLog::eLOG_TYPE_ERROR);
405 		return -1;
406 	}
407 	// Is the main node correct ?
408 	xmlElement = XmlGetFirstElement (m_XmlDoc, XNP_MAX_LANG_FILE);
409 	if (xmlElement == nullptr)
410 	{
411 		strErrorMsg = "Language file (" + szLanguageCode + "): missing main node!";
412 		Log.write (strErrorMsg, cLog::eLOG_TYPE_ERROR);
413 		return -1;
414 	}
415 
416 	// Who is responsible for the file ? (Who is to blame in case of errors?)
417 	xmlElement = XmlGetFirstElement (m_XmlDoc, XNP_MAX_LANG_FILE_FOOTER_AUTHOR);
418 	if (xmlElement == nullptr)
419 	{
420 		strErrorMsg = "Language file (" + szLanguageCode + "): missing author node!";
421 		Log.write (strErrorMsg, cLog::eLOG_TYPE_ERROR);
422 	}
423 	else
424 	{
425 		//TODO: Find the last editor of the XML file
426 	}
427 
428 	// Check the lang attribute of the main node
429 	xmlElement = XmlGetFirstElement (m_XmlDoc, XNP_MAX_LANG_FILE);
430 	const char* value = xmlElement->Attribute ("lang");
431 	if (value == nullptr)
432 	{
433 		strErrorMsg = "Language file (" + szLanguageCode + "): language attribut missing! Language can not be identified";
434 		Log.write (strErrorMsg, cLog::eLOG_TYPE_ERROR);
435 		return -1;
436 	}
437 	else if (szLanguageCode  != std::string (value))
438 	{
439 		strErrorMsg = "Language file (" + szLanguageCode + "): language attribut mismatch file name!";
440 		Log.write (strErrorMsg, cLog::eLOG_TYPE_ERROR);
441 		return -1;
442 	}
443 
444 	// Writing is left-to-right or vice versa ?
445 	value = xmlElement->Attribute ("direction");
446 	if (value == nullptr)
447 	{
448 		strErrorMsg = "Language file (" + szLanguageCode + "): language attribut 'direction' is missing! Writing direction will be set to 'Left-To-Right'";
449 		Log.write (strErrorMsg, cLog::eLOG_TYPE_WARNING);
450 		m_bLeftToRight = true;
451 	}
452 	else if (std::string (value) == "left-to-right")
453 	{
454 		m_bLeftToRight = true;
455 	}
456 	else if (std::string (value) == "right-to-left")
457 	{
458 		m_bLeftToRight = false;
459 	}
460 	else
461 	{
462 		strErrorMsg = "Language file (" + szLanguageCode + "): language attribut 'direction' can not interpreted! Writing direction will be set to 'Left-To-Right'";
463 		Log.write (strErrorMsg, cLog::eLOG_TYPE_WARNING);
464 		m_bLeftToRight = true;
465 	}
466 
467 	xmlElement = XmlGetFirstElement (m_XmlDoc, XNP_MAX_LANG_FILE_FOOTER_GAMEVERSION);
468 	if (xmlElement == nullptr)
469 	{
470 		strErrorMsg = "Language file (" + szLanguageCode + "): missing game version node!";
471 		Log.write (strErrorMsg, cLog::eLOG_TYPE_WARNING);
472 	}
473 	else
474 	{
475 		const char* value = xmlElement->Attribute ("time");
476 		if (value == nullptr)
477 		{
478 			strErrorMsg = "Language file (" + szLanguageCode + "): game version attribute 'time' is missing!";
479 			Log.write (strErrorMsg, cLog::eLOG_TYPE_WARNING);
480 		}
481 		else
482 		{
483 			int iTestResult = checkTimeStamp (value);
484 			switch (iTestResult)
485 			{
486 				case -1 :
487 					strErrorMsg = "Language file (" + szLanguageCode + "): game version attribute has wrong format!";
488 					Log.write (strErrorMsg, cLog::eLOG_TYPE_WARNING);
489 					break;
490 				case 0 :
491 					strErrorMsg = "Language file (" + szLanguageCode + "): may be outdated!";
492 					Log.write (strErrorMsg, cLog::eLOG_TYPE_WARNING);
493 					break;
494 				case 1 :
495 					strErrorMsg = "Language file (" + szLanguageCode + "): is newer than the game!";
496 					Log.write (strErrorMsg, cLog::eLOG_TYPE_WARNING);
497 					break;
498 				case 2 :
499 					// Timestamps match
500 					break;
501 			}
502 		}
503 	}
504 	return 0;
505 }
506 
507 // not const string& since it is modified
checkTimeStamp(std::string rstrData)508 int cLanguage::checkTimeStamp (std::string rstrData)
509 {
510 	//JCK: Should be replaced by a faster and more secure function
511 
512 	// Index   : 0123456789012345678
513 	// Example : 2007-09-30 13:04:00
514 
515 	if (rstrData.length() != 19) return -1;
516 	if (! isdigit (rstrData[0])) return -1;
517 	if (! isdigit (rstrData[1])) return -1;
518 	if (! isdigit (rstrData[2])) return -1;
519 	if (! isdigit (rstrData[3])) return -1;
520 	if (rstrData[4] != '-') return -1;
521 	if (! isdigit (rstrData[5])) return -1;
522 	if (! isdigit (rstrData[6])) return -1;
523 	if (rstrData[7] != '-') return -1;
524 	if (! isdigit (rstrData[8])) return -1;
525 	if (! isdigit (rstrData[9])) return -1;
526 	if (rstrData[10] != ' ') return -1;
527 	if (! isdigit (rstrData[11])) return -1;
528 	if (! isdigit (rstrData[12])) return -1;
529 	if (rstrData[13] != ':') return -1;
530 	if (! isdigit (rstrData[14])) return -1;
531 	if (! isdigit (rstrData[15])) return -1;
532 	if (rstrData[16] != ':') return -1;
533 	if (! isdigit (rstrData[17])) return -1;
534 	if (! isdigit (rstrData[18])) return -1;
535 
536 	std::string szTemp1 = rstrData;
537 
538 	szTemp1.erase (16, 1);
539 	szTemp1.erase (13, 1);
540 	szTemp1.erase (10, 1);
541 	szTemp1.erase (7, 1);
542 	szTemp1.erase (4, 1);
543 
544 	std::string szTemp2 = MAX_BUILD_DATE;
545 
546 	szTemp2.erase (16, 1);
547 	szTemp2.erase (13, 1);
548 	szTemp2.erase (10, 1);
549 	szTemp2.erase (7, 1);
550 	szTemp2.erase (4, 1);
551 
552 	if (szTemp1 < szTemp2)
553 	{
554 		return 0; // XML is older than the game
555 	}
556 	if (szTemp1 > szTemp2)
557 	{
558 		return 1; // XML is newer than the game
559 	}
560 	return 2;     // XML is matching the game
561 }
562 
ReadRecursiveLanguagePack(XMLElement * xmlElement,std::string strNodePath)563 int cLanguage::ReadRecursiveLanguagePack (XMLElement* xmlElement, std::string strNodePath)
564 {
565 	if (xmlElement == nullptr)
566 	{
567 		return -1;
568 	}
569 
570 	if (strNodePath[ strNodePath.length() - 1 ] != '~')
571 	{
572 		strNodePath += "~";
573 	}
574 
575 	const char* value = xmlElement->Attribute ("ENG");
576 	if (value != nullptr)
577 	{
578 		m_mpLanguage[strNodePath + xmlElement->Value()] = value;
579 		Log.write (strNodePath + xmlElement->Value() + " : " + value, cLog::eLOG_TYPE_DEBUG);
580 	}
581 
582 	XMLElement* xmlElementTMP = xmlElement->FirstChildElement();
583 	if (xmlElementTMP != nullptr)
584 	{
585 		this->ReadRecursiveLanguagePack (xmlElementTMP, strNodePath + xmlElement->Value());
586 	}
587 
588 	xmlElement = xmlElement->NextSiblingElement();
589 	if (xmlElement != nullptr)
590 	{
591 		this->ReadRecursiveLanguagePack (xmlElement, strNodePath);
592 	}
593 
594 	return 0;
595 }
596 
ReadSingleTranslation(const std::string & strInput)597 std::string cLanguage::ReadSingleTranslation (const std::string& strInput)
598 {
599 	if (strInput.empty()) return "";
600 	const std::string& strPath = strInput;
601 
602 	XMLElement* xmlElement = m_XmlDoc.RootElement();
603 	try
604 	{
605 		if (xmlElement == nullptr) throw std::string ("Can't excess root node");
606 		if (strcmp (xmlElement->Value(), "MAX_Language_File") != 0) throw std::string ("Root node mismatch");
607 	}
608 	catch (const std::string& strMsg)
609 	{
610 		Log.write ("Language file (" + m_szLanguage + "): " + strMsg, cLog::eLOG_TYPE_WARNING);
611 		return "";
612 	}
613 	std::size_t iPosBegin = 0;
614 	try
615 	{
616 		do
617 		{
618 			std::size_t iPosEnd = strPath.find ('~', iPosBegin + 1);
619 			std::string strCurrent = strPath.substr (iPosBegin, iPosEnd - iPosBegin);
620 			if (strCurrent[0] == '~') strCurrent.erase (0, 1);
621 			xmlElement = xmlElement->FirstChildElement (strCurrent.c_str());
622 			if (xmlElement == nullptr)
623 				throw i18n ("Text~Error_Messages~ERROR_Missing_Translation", m_mpLanguage[strInput]);
624 
625 			iPosBegin = iPosEnd;
626 		}
627 		while (iPosBegin != std::string::npos);
628 	}
629 	catch (const std::string& strMsg)
630 	{
631 		Log.write ("Language file (" + m_szLanguage + "): " + strMsg, cLog::eLOG_TYPE_WARNING);
632 		return m_mpLanguage[strInput];
633 	}
634 
635 	const char* translation = xmlElement->Attribute ("localized");
636 	if (translation != nullptr)
637 	{
638 		return translation;
639 	}
640 
641 	std::string szErrorMsg = "Language file: translation for >";
642 	szErrorMsg += strInput + "< is missing";
643 	Log.write (szErrorMsg, cLog::eLOG_TYPE_WARNING);
644 
645 	return "";
646 }
647