Files
itgmania212121/src/smpackage/LanguagesDlg.cpp
T
2011-03-17 01:47:30 -04:00

650 lines
19 KiB
C++

// 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()) );
}