text: // LanguagesDlg.cpp : implementation file
  
  #define CO_EXIST_WITH_MFC
  #include "global.h"
  #include "stdafx.h"
  #include "smpackage.h"
  #include "Foreach.h"
  #include "LanguagesDlg.h"
  #include "SpecialFiles.h"
  #include "RageUtil.h"
  #include "IniFile.h"
  #include "languagesdlg.h"
  #include "SMPackageUtil.h"
  #include "CreateLanguageDlg.h"
  #include "RageFile.h"
  #include "languagesdlg.h"
  #include "RageFileManager.h"
  #include ".\languagesdlg.h"
  #include "CsvFile.h"
  #include "archutils/Win32/DialogUtil.h"
  #include "LocalizedString.h"
  #include "arch/Dialog/Dialog.h"
  #include "archutils/Win32/SpecialDirs.h"
  #include "archutils/Win32/GotoURL.h"
  #include "archutils/Win32/ErrorStrings.h"
  
  // LanguagesDlg dialog
  
  IMPLEMENT_DYNAMIC(LanguagesDlg, CDialog)
  LanguagesDlg::LanguagesDlg(CWnd* pParent /*=NULL*/)
    : CDialog(LanguagesDlg::IDD, pParent)
  {
  }
  
  LanguagesDlg::~LanguagesDlg() {}
  
  void
  LanguagesDlg::DoDataExchange(CDataExchange* pDX)
  {
  	CDialog::DoDataExchange(pDX);
  	DDX_Control(pDX, IDC_LIST_THEMES, m_listThemes);
  	DDX_Control(pDX, IDC_LIST_LANGUAGES, m_listLanguages);
  	DDX_Control(pDX,
  				IDC_CHECK_EXPORT_ALREADY_TRANSLATED,
  				m_buttonExportAlreadyTranslated);
  }
  
  BOOL
  LanguagesDlg::OnInitDialog()
  {
  	CDialog::OnInitDialog();
  
  	DialogUtil::LocalizeDialogAndContents(*this);
  
  	vector<RString> vs;
  	GetDirListing(SpecialFiles::THEMES_DIR + "*", vs, true);
  	StripCvsAndSvn(vs);
  	FOREACH_CONST(RString, vs, s)
  	m_listThemes.AddString(*s);
  	if (!vs.empty())
  		m_listThemes.SetSel(0);
  
  	OnSelchangeListThemes();
  
  	return TRUE; // return TRUE  unless you set the focus to a control
  }
  
  static RString
  GetCurrentString(const CListBox& list)
  {
  	// TODO: Add your control notification handler code here
  	int iSel = list.GetCurSel();
  	if (iSel == LB_ERR)
  		return RString();
  	CString s;
  	list.GetText(list.GetCurSel(), s);
  	return RString(s);
  }
  
  static void
  SelectString(CListBox& list, const RString& sToSelect)
  {
  	for (int i = 0; i < list.GetCount(); i++) {
  		CString s;
  		list.GetText(i, s);
  		if (s == sToSelect) {
  			list.SetCurSel(i);
  			break;
  		}
  	}
  }
  
  void
  LanguagesDlg::OnSelchangeListThemes()
  {
  	// TODO: Add your control notification handler code here
  	m_listLanguages.ResetContent();
  
  	RString sTheme = GetCurrentString(m_listThemes);
  	if (!sTheme.empty()) {
  		RString sLanguagesDir = SpecialFiles::THEMES_DIR + sTheme + "/" +
  								SpecialFiles::LANGUAGES_SUBDIR;
  
  		vector<RString> vs;
  		GetDirListing(sLanguagesDir + "*.ini", vs, false);
  		FOREACH_CONST(RString, vs, s)
  		{
  			RString sIsoCode = GetFileNameWithoutExtension(*s);
  			RString sLanguage =
  			  SMPackageUtil::GetLanguageDisplayString(sIsoCode);
  			m_listLanguages.AddString(ConvertUTF8ToACP(sLanguage));
  		}
  		if (!vs.empty())
  			m_listLanguages.SetSel(0);
  	}
  
  	OnSelchangeListLanguages();
  }
  
  static RString
  GetLanguageFile(const RString& sTheme, const RString& sLanguage)
  {
  	return SpecialFiles::THEMES_DIR + sTheme + "/" +
  		   SpecialFiles::LANGUAGES_SUBDIR + sLanguage + ".ini";
  }
  
  static int
  GetNumValuesInIniFile(const RString& sIniFile)
  {
  	int count = 0;
  	IniFile ini;
  	ini.ReadFile(sIniFile);
  	FOREACH_CONST_Child(&ini, key) { FOREACH_CONST_Attr(key, value) count++; }
  	return count;
  }
  
  static int
  GetNumIntersectingIniValues(const RString& sIniFile1, const RString& sIniFile2)
  {
  	int count = 0;
  	IniFile ini1;
  	ini1.ReadFile(sIniFile1);
  	IniFile ini2;
  	ini2.ReadFile(sIniFile2);
  	FOREACH_CONST_Child(&ini1, key1)
  	{
  		const XNode* key2 = ini2.GetChild(key1->m_sName);
  		if (key2 == NULL)
  			continue;
  		FOREACH_CONST_Attr(key1, attr1)
  		{
  			if (key2->GetAttr(attr1->first) == NULL)
  				continue;
  			count++;
  		}
  	}
  	return count;
  }
  
  void
  LanguagesDlg::OnSelchangeListLanguages()
  {
  	// TODO: Add your control notification handler code here
  	int iTotalStrings = -1;
  	int iNeedTranslation = -1;
  
  	RString sTheme = GetCurrentString(m_listThemes);
  	RString sLanguage = GetCurrentString(m_listLanguages);
  
  	if (!sTheme.empty()) {
  		RString sBaseLanguageFile =
  		  GetLanguageFile(sTheme, SpecialFiles::BASE_LANGUAGE);
  		iTotalStrings = GetNumValuesInIniFile(sBaseLanguageFile);
  
  		if (!sLanguage.empty()) {
  			sLanguage =
  			  SMPackageUtil::GetLanguageCodeFromDisplayString(sLanguage);
  
  			RString sLanguageFile = GetLanguageFile(sTheme, sLanguage);
  			iNeedTranslation =
  			  iTotalStrings -
  			  GetNumIntersectingIniValues(sBaseLanguageFile, sLanguageFile);
  		}
  	}
  
  	GetDlgItem(IDC_STATIC_TOTAL_STRINGS)
  	  ->SetWindowText(ssprintf(iTotalStrings == -1 ? "" : "%d", iTotalStrings));
  	GetDlgItem(IDC_STATIC_NEED_TRANSLATION)
  	  ->SetWindowText(
  		ssprintf(iNeedTranslation == -1 ? "" : "%d", iNeedTranslation));
  
  	GetDlgItem(IDC_BUTTON_CREATE)->EnableWindow(!sTheme.empty());
  	GetDlgItem(IDC_BUTTON_DELETE)->EnableWindow(!sLanguage.empty());
  	GetDlgItem(IDC_BUTTON_EXPORT)->EnableWindow(!sLanguage.empty());
  	GetDlgItem(IDC_BUTTON_IMPORT)->EnableWindow(!sLanguage.empty());
  	GetDlgItem(IDC_CHECK_LANGUAGE)->EnableWindow(!sLanguage.empty());
  	GetDlgItem(IDC_CHECK_EXPORT_ALREADY_TRANSLATED)
  	  ->EnableWindow(!sLanguage.empty());
  }
  
  BEGIN_MESSAGE_MAP(LanguagesDlg, CDialog)
  ON_LBN_SELCHANGE(IDC_LIST_THEMES, OnSelchangeListThemes)
  ON_LBN_SELCHANGE(IDC_LIST_LANGUAGES, OnSelchangeListLanguages)
  ON_BN_CLICKED(IDC_BUTTON_CREATE, OnBnClickedButtonCreate)
  ON_BN_CLICKED(IDC_BUTTON_DELETE, OnBnClickedButtonDelete)
  ON_BN_CLICKED(IDC_BUTTON_EXPORT, OnBnClickedButtonExport)
  ON_BN_CLICKED(IDC_BUTTON_IMPORT, OnBnClickedButtonImport)
  ON_BN_CLICKED(IDC_CHECK_LANGUAGE, OnBnClickedCheckLanguage)
  END_MESSAGE_MAP()
  
  // LanguagesDlg message handlers
  
  void
  LanguagesDlg::OnBnClickedButtonCreate()
  {
  	// TODO: Add your control notification handler code here
  	CreateLanguageDlg dlg;
  	int nResponse = dlg.DoModal();
  	if (nResponse != IDOK)
  		return;
  
  	RString sTheme = GetCurrentString(m_listThemes);
  	ASSERT(!sTheme.empty());
  
  	RString sLanguageFile =
  	  GetLanguageFile(sTheme, RString(dlg.m_sChosenLanguageCode));
  
  	// create empty file
  	RageFile file;
  	file.Open(sLanguageFile, RageFile::WRITE);
  	file.Close(); // flush file
  
  	OnSelchangeListThemes();
  	SelectString(m_listLanguages,
  				 SMPackageUtil::GetLanguageDisplayString(
  				   RString(dlg.m_sChosenLanguageCode)));
  	OnSelchangeListLanguages();
  }
  
  static LocalizedString THIS_WILL_PERMANENTLY_DELETE(
    "LanguagesDlg",
    "This will permanently delete '%s'. Continue?");
  void
  LanguagesDlg::OnBnClickedButtonDelete()
  {
  	// TODO: Add your control notification handler code here
  	RString sTheme = GetCurrentString(m_listThemes);
  	ASSERT(!sTheme.empty());
  	RString sLanguage = GetCurrentString(m_listLanguages);
  	ASSERT(!sLanguage.empty());
  	sLanguage = SMPackageUtil::GetLanguageCodeFromDisplayString(sLanguage);
  
  	RString sLanguageFile = GetLanguageFile(sTheme, sLanguage);
  
  	RString sMessage =
  	  ssprintf(THIS_WILL_PERMANENTLY_DELETE.GetValue(), sLanguageFile.c_str());
  	Dialog::Result ret = Dialog::AbortRetry(sMessage);
  	if (ret != Dialog::retry)
  		return;
  
  	FILEMAN->Remove(sLanguageFile);
  
  	OnSelchangeListThemes();
  }
  
  struct TranslationLine
  {
  	RString sSection, sID, sBaseLanguage, sCurrentLanguage;
  };
  
  static LocalizedString FAILED_TO_SAVE("LanguagesDlg", "Failed to save '%s'.");
  static LocalizedString THERE_ARE_NO_STRINGS_TO_EXPORT(
    "LanguagesDlg",
    "There are no strings to export for this language.");
  static LocalizedString EXPORTED_TO_YOUR_DESKTOP(
    "LanguagesDlg",
    "Exported to your Desktop as '%s'.");
  void
  LanguagesDlg::OnBnClickedButtonExport()
  {
  	// TODO: Add your control notification handler code here
  	RString sTheme = GetCurrentString(m_listThemes);
  	ASSERT(!sTheme.empty());
  	RString sLanguage = GetCurrentString(m_listLanguages);
  	ASSERT(!sLanguage.empty());
  	sLanguage = SMPackageUtil::GetLanguageCodeFromDisplayString(sLanguage);
  
  	RString sBaseLanguageFile =
  	  GetLanguageFile(sTheme, SpecialFiles::BASE_LANGUAGE);
  	RString sLanguageFile = GetLanguageFile(sTheme, sLanguage);
  
  	bool bExportAlreadyTranslated =
  	  !!m_buttonExportAlreadyTranslated.GetCheck();
  
  	CsvFile csv;
  	IniFile ini1;
  	ini1.ReadFile(sBaseLanguageFile);
  	IniFile ini2;
  	ini2.ReadFile(sLanguageFile);
  	int iNumExpored = 0;
  
  	vector<RString> vs;
  	vs.push_back("Section");
  	vs.push_back("ID");
  	vs.push_back("Base Language Value");
  	vs.push_back("Localized Value");
  	csv.m_vvs.push_back(vs);
  
  	FOREACH_CONST_Child(&ini1, key)
  	{
  		FOREACH_CONST_Attr(key, value)
  		{
  			TranslationLine tl;
  			tl.sSection = key->m_sName;
  			tl.sID = value->first;
  			tl.sBaseLanguage = value->second->GetValue<RString>();
  			ini2.GetValue(tl.sSection, tl.sID, tl.sCurrentLanguage);
  
  			// don't export empty strings
  			RString sBaseLanguageTrimmed = tl.sBaseLanguage;
  			TrimLeft(sBaseLanguageTrimmed, " ");
  			TrimRight(sBaseLanguageTrimmed, " ");
  			if (sBaseLanguageTrimmed.empty())
  				continue;
  
  			bool bAlreadyTranslated = !tl.sCurrentLanguage.empty();
  			if (!bAlreadyTranslated || bExportAlreadyTranslated) {
  				vector<RString> vs;
  				vs.push_back(tl.sSection);
  				vs.push_back(tl.sID);
  				vs.push_back(tl.sBaseLanguage);
  				vs.push_back(tl.sCurrentLanguage);
  				csv.m_vvs.push_back(vs);
  				iNumExpored++;
  			}
  		}
  	}
  
  	RageFileOsAbsolute file;
  	RString sFile = sTheme + "-" + sLanguage + ".csv";
  	RString sFullFile = SpecialDirs::GetDesktopDir() + sFile;
  	file.Open(sFullFile, RageFile::WRITE);
  	if (iNumExpored == 0) {
  		Dialog::OK(THERE_ARE_NO_STRINGS_TO_EXPORT.GetValue());
  		return;
  	}
  
  	if (csv.WriteFile(file))
  		Dialog::OK(
  		  ssprintf(EXPORTED_TO_YOUR_DESKTOP.GetValue(), sFile.c_str()));
  	else
  		Dialog::OK(ssprintf(FAILED_TO_SAVE.GetValue(), sFullFile.c_str()));
  }
  
  static LocalizedString ERROR_READING_FILE("LanguagesDlg",
  										  "Error reading file '%s'.");
  static LocalizedString ERROR_PARSING_FILE("LanguagesDlg",
  										  "Error parsing file '%s'.");
  static LocalizedString IMPORTING_THESE_STRINGS_WILL_OVERRIDE(
    "LanguagesDlg",
    "Importing these strings will override all data in '%s'. Continue?");
  static LocalizedString ERROR_READING("LanguagesDlg", "Error reading '%s'.");
  static LocalizedString ERROR_READING_EACH_LINE_MUST_HAVE(
    "LanguagesDlg",
    "Error reading line '%s' from '%s': Each line must have either 3 or 4 "
    "values.  This row has %d values.");
  static LocalizedString IMPORTED_STRINGS_INTO("LanguagesDlg",
  											 "Imported %d strings into '%s'.\n "
  											 "- %d were added.\n - %d were "
  											 "modified\n - %d were unchanged\n "
  											 "- %d empty strings were ignored");
  void
  LanguagesDlg::OnBnClickedButtonImport()
  {
  	// TODO: Add your control notification handler code here
  	CFileDialog dialog(TRUE, // file open?
  					   NULL, // default file extension
  					   NULL, // default file name
  					   OFN_HIDEREADONLY | OFN_NOCHANGEDIR, // flags
  					   "CSV file (*.csv)|*.csv|||");
  	int iRet = dialog.DoModal();
  	RString sCsvFile = dialog.GetPathName();
  	if (iRet != IDOK)
  		return;
  
  	RString sTheme = GetCurrentString(m_listThemes);
  	ASSERT(!sTheme.empty());
  	RString sLanguage = GetCurrentString(m_listLanguages);
  	ASSERT(!sLanguage.empty());
  	sLanguage = SMPackageUtil::GetLanguageCodeFromDisplayString(sLanguage);
  
  	RageFileOsAbsolute cvsFile;
  	if (!cvsFile.Open(sCsvFile)) {
  		Dialog::OK(ssprintf(ERROR_READING_FILE.GetValue(), sCsvFile.c_str()));
  		return;
  	}
  	CsvFile csv;
  	if (!csv.ReadFile(cvsFile)) {
  		Dialog::OK(ssprintf(ERROR_PARSING_FILE.GetValue(), sCsvFile.c_str()));
  		return;
  	}
  
  	RString sLanguageFile = GetLanguageFile(sTheme, sLanguage);
  
  	{
  		Dialog::Result ret = Dialog::AbortRetry(
  		  ssprintf(IMPORTING_THESE_STRINGS_WILL_OVERRIDE.GetValue(),
  				   sLanguageFile.c_str()));
  		if (ret != Dialog::retry)
  			return;
  	}
  
  	IniFile ini;
  	if (!ini.ReadFile(sLanguageFile)) {
  		Dialog::OK(ssprintf(ERROR_READING.GetValue(), sLanguageFile.c_str()));
  		return;
  	}
  
  	int iNumImported = 0;
  	int iNumAdded = 0;
  	int iNumModified = 0;
  	int iNumUnchanged = 0;
  	int iNumIgnored = 0;
  	FOREACH_CONST(CsvFile::StringVector, csv.m_vvs, line)
  	{
  		int iLineIndex = line - csv.m_vvs.begin();
  
  		// Skip the header row
  		if (iLineIndex == 0)
  			continue;
  
  		TranslationLine tl;
  		int iNumValues = line->size();
  		if (iNumValues != 3 && iNumValues != 4) {
  			Dialog::OK(ssprintf(ERROR_READING_EACH_LINE_MUST_HAVE.GetValue(),
  								join(",", *line).c_str(),
  								sCsvFile.c_str(),
  								iNumValues));
  			return;
  		}
  		tl.sSection = (*line)[0];
  		tl.sID = (*line)[1];
  		tl.sBaseLanguage = (*line)[2];
  		tl.sCurrentLanguage = iNumValues == 4 ? (*line)[3] : RString();
  
  		if (tl.sCurrentLanguage.empty()) {
  			iNumIgnored++;
  		} else {
  			RString sOldCurrentLanguage;
  			bool bExists =
  			  ini.GetValue(tl.sSection, tl.sID, sOldCurrentLanguage);
  			if (bExists) {
  				if (sOldCurrentLanguage == tl.sCurrentLanguage)
  					iNumUnchanged++;
  				else
  					iNumModified++;
  			} else {
  				iNumAdded++;
  			}
  
  			ini.SetValue(tl.sSection, tl.sID, tl.sCurrentLanguage);
  			iNumImported++;
  		}
  	}
  
  	if (ini.WriteFile(sLanguageFile))
  		Dialog::OK(ssprintf(IMPORTED_STRINGS_INTO.GetValue(),
  							iNumImported,
  							sLanguageFile.c_str(),
  							iNumAdded,
  							iNumModified,
  							iNumUnchanged,
  							iNumIgnored));
  	else
  		Dialog::OK(ssprintf(FAILED_TO_SAVE.GetValue(), sLanguageFile.c_str()));
  
  	OnSelchangeListThemes();
  }
  
  void
  GetAllMatches(const RString& sRegex,
  			  const RString& sString,
  			  vector<RString>& asOut)
  {
  	Regex re(sRegex + "(.*)$");
  	RString sMatch(sString);
  	while (1) {
  		vector<RString> asMatches;
  		if (!re.Compare(sMatch, asMatches))
  			break;
  		asOut.push_back(asMatches[0]);
  		sMatch = asMatches[1];
  	}
  }
  
  void
  DumpList(const vector<RString>& asList, RageFile& file)
  {
  	RString sLine;
  	FOREACH_CONST(RString, asList, s)
  	{
  		if (sLine.size() + s->size() > 100) {
  			file.PutLine(sLine);
  			sLine = "";
  		}
  
  		if (sLine.size())
  			sLine += ", ";
  		else
  			sLine += "    ";
  
  		sLine += *s;
  	}
  
  	file.PutLine(sLine);
  }
  
  void
  LanguagesDlg::OnBnClickedCheckLanguage()
  {
  	RString sTheme = GetCurrentString(m_listThemes);
  	ASSERT(!sTheme.empty());
  	RString sLanguage = GetCurrentString(m_listLanguages);
  	ASSERT(!sLanguage.empty());
  	sLanguage = SMPackageUtil::GetLanguageCodeFromDisplayString(sLanguage);
  
  	RString sBaseLanguageFile =
  	  GetLanguageFile(sTheme, SpecialFiles::BASE_LANGUAGE);
  	RString sLanguageFile = GetLanguageFile(sTheme, sLanguage);
  
  	IniFile ini1;
  	ini1.ReadFile(sBaseLanguageFile);
  	IniFile ini2;
  	ini2.ReadFile(sLanguageFile);
  
  	RageFileOsAbsolute file;
  	RString sFile = sTheme + "-" + sLanguage + " check.txt";
  	RString sFullFile = SpecialDirs::GetDesktopDir() + sFile;
  	file.Open(sFullFile, RageFile::WRITE);
  
  	{ FOREACH_CONST_Child(&ini1, key){
  	  FOREACH_CONST_Attr(key, value){ const RString& sSection = key->m_sName;
  	const RString& sID = value->first;
  	const RString& sBaseLanguage = value->second->GetValue<RString>();
  	RString sCurrentLanguage;
  	ini2.GetValue(sSection, sID, sCurrentLanguage);
  
  	if (sCurrentLanguage.empty())
  		continue;
  
  	/* Check &FOO;, %{foo}, %-1.2f, %.  Some mismatches here are normal,
  	 * particularly in languages with substantially different word order rules
  	 * than English. */
  	{
  		RString sRegex =
  		  "(&[A-Za-z]+;|%{[A-Za-z]+}|%[+-]?[0-9]*.?[0-9]*[sidf]|%)";
  		vector<RString> asMatchesBase;
  		GetAllMatches(sRegex, sBaseLanguage, asMatchesBase);
  
  		vector<RString> asMatches;
  		GetAllMatches(sRegex, sCurrentLanguage, asMatches);
  
  		if (asMatchesBase.size() != asMatches.size() ||
  			!std::equal(
  			  asMatchesBase.begin(), asMatchesBase.end(), asMatches.begin())) {
  			file.PutLine(
  			  ssprintf("Entity/substitution mismatch in section [%s] (%s):",
  					   sSection.c_str(),
  					   sID.c_str()));
  			file.PutLine(ssprintf("    %s", sCurrentLanguage.c_str()));
  			file.PutLine(ssprintf("    %s", sBaseLanguage.c_str()));
  		}
  	}
  
  	/* Check "foo::bar", "foo\nbar" and double quotes.  Check these
  	 * independently of the above. */
  	{
  		RString sRegex = "(\"|::|\\n)";
  		vector<RString> asMatchesBase;
  		GetAllMatches(sRegex, sBaseLanguage, asMatchesBase);
  
  		vector<RString> asMatches;
  		GetAllMatches(sRegex, sCurrentLanguage, asMatches);
  
  		if (asMatchesBase.size() != asMatches.size() ||
  			!std::equal(
  			  asMatchesBase.begin(), asMatchesBase.end(), asMatches.begin())) {
  			file.PutLine(
  			  ssprintf("Quote/line break mismatch in section [%s] (%s):",
  					   sSection.c_str(),
  					   sID.c_str()));
  			file.PutLine(ssprintf("    %s", sCurrentLanguage.c_str()));
  			file.PutLine(ssprintf("    %s", sBaseLanguage.c_str()));
  		}
  	}
  
  	/* Check that both end in a period or both don't end an a period. */
  	{
  		RString sBaseLanguage2 = sBaseLanguage;
  		TrimRight(sBaseLanguage2, " ");
  		RString sCurrentLanguage2 = sCurrentLanguage;
  		TrimRight(sCurrentLanguage2, " ");
  
  		if ((sBaseLanguage2.Right(1) == ".") ^
  			(sCurrentLanguage2.Right(1) == ".")) {
  			file.PutLine(ssprintf("Period mismatch in section [%s] (%s):",
  								  sSection.c_str(),
  								  sID.c_str()));
  			file.PutLine(ssprintf("    %s", sCurrentLanguage.c_str()));
  			file.PutLine(ssprintf("    %s", sBaseLanguage.c_str()));
  		}
  	}
  }
  }
  }
  
  { FOREACH_CONST_Child(&ini2, key){
    FOREACH_CONST_Attr(key, value){ const RString& sSection = key->m_sName;
  const RString& sID = value->first;
  const RString& sCurrentLanguage = value->second->GetValue<RString>();
  if (utf8_is_valid(sCurrentLanguage))
  	continue;
  
  /* The text isn't valid UTF-8.  We don't know what encoding it is; guess that
   * it's ISO-8859-1 and convert it so it'll display correctly and can be pasted
   * into the file.  Don't include the original text: if we include multiple
   * encodings in the resulting text file, editors won't understand the encoding
   * of the file and will pick one arbitrarily. */
  file.PutLine(ssprintf("Incorrect encoding in section [%s]:", sSection.c_str()));
  wstring wsConverted = ConvertCodepageToWString(sCurrentLanguage, 1252);
  RString sConverted = WStringToRString(wsConverted);
  file.PutLine(ssprintf("%s=%s", sID.c_str(), sConverted.c_str()));
  }
  }
  }
  
  { FOREACH_CONST_Child(&ini2, key){ vector<RString> asList;
  const RString& sSection = key->m_sName;
  FOREACH_CONST_Attr(key, value)
  {
  	const RString& sID = value->first;
  	RString sCurrentLanguage;
  	if (ini1.GetValue(sSection, sID, sCurrentLanguage))
  		continue;
  
  	asList.push_back(sID);
  }
  if (!asList.empty()) {
  	file.PutLine(
  	  ssprintf("Obsolete translation in section [%s]:", sSection.c_str()));
  	DumpList(asList, file);
  }
  }
  }
  
  {
  	FOREACH_CONST_Child(&ini1, key)
  	{
  		vector<RString> asList;
  		const RString& sSection = key->m_sName;
  		FOREACH_CONST_Attr(key, value)
  		{
  			const RString& sID = value->first;
  			const RString& sBaseText = value->second->GetValue<RString>();
  			if (sBaseText.empty())
  				continue;
  			RString sCurrentLanguage;
  			if (ini2.GetValue(sSection, sID, sCurrentLanguage))
  				continue;
  
  			asList.push_back(sID);
  		}
  		if (!asList.empty()) {
  			file.PutLine(
  			  ssprintf("Not translated in section [%s]:", sSection.c_str()));
  			DumpList(asList, file);
  		}
  	}
  }
  
  file.Close();
  
  GotoURL(ssprintf("file://%s", sFullFile.c_str()));
  }
  