Click here to Skip to main content
15,881,281 members
Articles / Desktop Programming / MFC

RC Localization Tool (LocalizeRC)

Rate me:
Please Sign up or sign in to vote.
4.89/5 (53 votes)
14 Jan 2004BSD9 min read 486.4K   9.5K   159  
A tool for localizing/translating Resource Scripts
// LocalizeRCDlg.cpp : implementation file
//

#include "stdafx.h"
#include "LocalizeRC.h"
#include "LocalizeRCDlg.h"

#include <math.h>

#define OLDFILEFORMAT -2

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

//#define _MFC_VER 0x0420

// CAboutDlg dialog used for App About
class CAboutDlg : public CDialog
{
public:
	CAboutDlg();

// Dialog Data
	enum { IDD = IDD_ABOUTBOX };

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

// Implementation
protected:
	DECLARE_MESSAGE_MAP()
private:
	CString m_strAbout;
public:
	virtual BOOL OnInitDialog();
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
, m_strAbout(_T(""))
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Text(pDX, IDC_ABOUT, m_strAbout);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
END_MESSAGE_MAP()


BOOL CAboutDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	m_strAbout.LoadString( IDS_ABOUT );
	UpdateData( false );

	return TRUE;  // return TRUE unless you set the focus to a control
	// EXCEPTION: OCX Property Pages should return FALSE
}

// CLocalizeRCDlg dialog
CLocalizeRCDlg::CLocalizeRCDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CLocalizeRCDlg::IDD, pParent)
	, m_bCopy(FALSE)
	, m_nObsoleteItems(0)
	, m_strWorkspace(_T(""))
	, m_strEdit(_T(""))
	, m_strTextmode(_T(""))
	, m_bNoSort(FALSE)
	, m_strInputRC(_T(""))
	, m_strLangINI(_T(""))
	, m_strOutputRC(_T(""))
	, m_strAbout(_T(""))
{
	// last used Workspace from registry
	m_strWorkspace = AfxGetApp()->GetProfileString( SEC_LASTPROJECT, ENT_WORKSPACE );
}

void CLocalizeRCDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Check(pDX, IDC_COPY, m_bCopy);
	DDX_CBIndex(pDX, IDC_OBS_ITEMS, m_nObsoleteItems);
	DDX_Text(pDX, IDC_WORKSPACE, m_strWorkspace);
	DDX_Control(pDX, IDC_LANGUAGE, m_CtrlLanguage);
	DDX_Text(pDX, IDC_EDIT, m_strEdit);
	DDX_Text(pDX, IDC_TEXTMODE, m_strTextmode);
	DDX_Check(pDX, IDC_NOSORT, m_bNoSort);
	DDX_Text(pDX, IDC_INPUTRC, m_strInputRC);
	DDX_Text(pDX, IDC_LANGINI, m_strLangINI);
	DDX_Text(pDX, IDC_OUTPUTRC, m_strOutputRC);
	DDX_Text(pDX, IDC_ABOUT, m_strAbout);
}

BEGIN_MESSAGE_MAP(CLocalizeRCDlg, CDialog)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	//}}AFX_MSG_MAP
	ON_BN_CLICKED(IDC_CREATEINI, OnBnClickedCreateini)
	ON_BN_CLICKED(IDC_OPENINI, OnBnClickedOpenini)
	ON_BN_CLICKED(IDC_CREATEOUTPUT, OnBnClickedCreateoutput)
	ON_WM_DESTROY()
	ON_BN_CLICKED(IDC_REVERSEINI, OnBnClickedReverseini)
	ON_BN_CLICKED(IDC_CHNG_WORKSPACE, OnBnClickedChngWorkspace)
	ON_CBN_SELCHANGE(IDC_LANGUAGE, OnCbnSelchangeLanguage)
	ON_CBN_SELCHANGE(IDC_OBS_ITEMS, OnCbnSelchangeObsItems)
	ON_BN_KILLFOCUS(IDC_COPY, OnBnKillfocusCopy)
	ON_BN_KILLFOCUS(IDC_NOSORT, OnBnKillfocusNosort)
	ON_BN_CLICKED(IDC_NEW_WORKSPACE, OnBnClickedNewWorkspace)
	ON_BN_CLICKED(IDC_CHNG_INPUTRC, OnBnClickedChngInputrc)
	ON_BN_CLICKED(IDC_CHNG_LANGINI, OnBnClickedChngLangini)
	ON_BN_CLICKED(IDC_CHNG_OUTPUTRC, OnBnClickedChngOutputrc)
END_MESSAGE_MAP()


// CLocalizeRCDlg message handlers
BOOL CLocalizeRCDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// Add "About..." menu item to system menu.

	// IDM_ABOUTBOX must be in the system command range.
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		CString strAboutMenu;
		strAboutMenu.LoadString(IDS_ABOUTBOX);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// LoadIcon only loads the 32x32 icon, therefore use ::LoadImage for other sizes (16x16)
	hLargeIcon = AfxGetApp()->LoadIcon ( IDR_MAINFRAME );
	hSmallIcon = (HICON) ::LoadImage ( AfxGetResourceHandle(), 
                                         MAKEINTRESOURCE(IDR_MAINFRAME),
                                         IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR );

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(hLargeIcon, TRUE);		// Set big icon
	SetIcon(hSmallIcon, FALSE);		// Set small icon

	// display text mode (UNICODE/ANSI)
#ifdef UNICODE
	m_strTextmode.LoadString( IDS_UNICODE );
#else
	m_strTextmode.LoadString( IDS_ANSI );
#endif
	m_strAbout.LoadString( IDS_ABOUT );
	UpdateData(false);

	// get installed languages
	CFileFind Finder;
	TCHAR szFilename[MAX_PATH];
	GetModuleFileName( NULL, szFilename, MAX_PATH );
	
	AddLanguage( &m_CtrlLanguage, _T("09"), LangID );
	
	// the last 2 chars are the LanguageID in hexadecimal form
	CString strSearch;
	strSearch.Format( _T("%sLocalizeRC??.dll"), GetFolder(szFilename) );

	BOOL bResult = Finder.FindFile( strSearch );
	CString strLangCode;

	while( bResult )
	{
		bResult = Finder.FindNextFile();
		// extract 2-digit language code
		strLangCode = Finder.GetFileTitle().Right(2);
		AddLanguage( &m_CtrlLanguage, strLangCode, LangID );
	}
	Finder.Close();

	// Load last workspace
	LoadWorkspace(false);

	return TRUE;  // return TRUE  unless you set the focus to a control
}

void CLocalizeRCDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialog::OnSysCommand(nID, lParam);
	}
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CLocalizeRCDlg::OnPaint() 
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, hSmallIcon);
	}
	else
	{
		CDialog::OnPaint();
	}
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CLocalizeRCDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(hSmallIcon);
}

#define NUMKEYWORDS 8
LPCTSTR strKeyword[NUMKEYWORDS] = 
{
	_T("ACCELERATORS"),
	_T("DIALOG"),
	_T("DIALOGEX"),
	_T("MENU"),
	_T("MENUEX"),
	_T("STRINGTABLE"),
	_T("DLGINIT"),
	_T("code_page")
};
	
// random string, that indicates an error
#define ERR_STR _T("asfdshkagzuwrthgadsfhgkh12385143258")

BOOL CLocalizeRCDlg::OpenInputRC(BOOL bShowError)
{
	// The file from which to load the contents of rich edit control
	CStdioFile* pFile = NULL;
	
	if( m_strInputRC.IsEmpty() )
	{
		if( bShowError )
			AfxMessageBox( IDS_ERR_FILENAMEEMPTY );
		// clear contents in edit-control
		m_strEdit = _T("");
		UpdateData( false );
		return false;
	}

	BOOL bIsUnicode = CStdioUnicodeFile::IsUnicode( m_strInputRC );

	try
	{
	#ifdef UNICODE	
		if( bIsUnicode )
		{
			pFile = new CStdioUnicodeFile( m_strInputRC, CFile::modeRead | CFILEFLAG_UNICODEHELPER );
			// skip first two bytes
			pFile->Seek( 2, CFile::begin );
		}
		else
			pFile = new CStdioUnicodeFile( m_strInputRC, CFile::modeRead );
	#else
		if( bIsUnicode )
		{
			CString strErr;
			strErr.Format( IDS_UNICODEFILE, m_strInputRC );
			AfxMessageBox( strErr );

			// clear contents in edit-control
			m_strEdit = _T("");
			UpdateData( false );
			return false;	// cancel
		}
		else
			pFile = new CStdioUnicodeFile( m_strInputRC, CFile::modeRead );
	#endif
	}
	catch( CFileException* e )
	{
		TCHAR szCause[255];
		e->GetErrorMessage(szCause, 255);
		AfxMessageBox(szCause);
		e->Delete();

		// clear contents in edit-control
		m_strEdit = _T("");
		UpdateData( false );
		return false;	// cancel
	}
	// clear contents in edit-control
	m_strEdit = _T("");

	// fill edit-control with contents of the RC-file
	CString strLine;
	while( pFile->ReadString( strLine ) )
		m_strEdit += strLine + _T("\r\n");
	UpdateData( false );

	pFile->Close();
	delete pFile;
	return true;
}

void CLocalizeRCDlg::OnBnClickedCreateini()
{
	// Reload InputRC
	if( !OpenInputRC() )
		return;
	// Write/Actualize INI-File
    if( WriteReadIni(true) == OLDFILEFORMAT )
		AfxMessageBox( IDS_OLDFILEFORMAT );
}

// extract first caption after *pnPosition
CString CLocalizeRCDlg::ExtractCaption(CString& strText, int* pnPosition, CString strKeyword, CString &strIDC )
{
	CString strCaption, strLine;
	int nStart, nEnd, nStartQuote, nEndQuote;

	nStart = *pnPosition;

	// DLGINIT: Microsoft-Format to store data for comboboxes
	if( strKeyword == _T("DLGINIT") )
	{		
		if( nStart == 0 )
		{
			// remove BEGIN and END
			nStart = FindSeperateWord( strText, _T("BEGIN"), 0 );
			if( nStart != -1 )
				nStart += 7;	
		}

		// FORMAT: <IDC>, 0x403, <LENGTH IN BYTES>, 0
		// FORMAT: <WORD> as Hex Wert chars in LOBYTE und HIGHBYTE
		// comma seperated, null-terminated, ends with comma

		// comma seperated, null-terminated, ends with comma
		CString strHelp;
		LPTSTR pHelp;
		int nWord, nToken = 0, nLength = 0;

		CString strToken = StringTokenize( strText, _T(",\r\n"), &nStart);
		while (nStart != -1)
		{
			strToken.TrimLeft();
			strToken.TrimRight();
			if( strToken.IsEmpty() )
			{
				strToken = StringTokenize( strText, _T(",\r\n"),&nStart);
				continue;
			}

			switch( nToken )
			{
				case 0:		// IDC	oder 0 bei Ende von Liste
					if( strToken == "0" )
					{
						*pnPosition = nStart;
						return strCaption;
					}
					else // IDC
					{
						if( strIDC.IsEmpty() )
						{
							strIDC = strToken; 	// new IDC
						}
						else
						{
							if( strIDC != strToken )
								return strCaption;       
						}
					}

					break;
				case 1:		// 0x403 for Combo-Boxes
					if( strToken != "0x403" )
						return ERR_STR;
					break;
				case 2:		// Length of String
					nLength = _ttoi( strToken );
					nLength = (int)floor((nLength / 2.0)+0.5);
					break;
				case 3:		// always 0
					break;
				default:	// everything above 3 is word values
					if( nToken <= nLength + 3 )
					{
						// convert string to hexadecimal integer
						nWord = _tcstol( strToken, &pHelp, 16 );
						strHelp.Format( _T("%c%c"), LOBYTE(nWord), HIBYTE(nWord) );
						strHelp.Remove( '\0' );
						strCaption += strHelp;
						if( nToken == nLength + 3 )	// last word
						{
							strCaption += ";";
							nToken = -1;
						}
					}
			}
			nToken++;
			*pnPosition = nStart;
			strToken = StringTokenize(strText, _T(",\r\n"), &nStart);
		}
		return ERR_STR;
	}	

	// find end of line
	while( (nEnd = strText.Find( _T("\r\n"), nStart )) != -1 )
	{
		// strLine contents one command line
		strLine += strText.Mid( nStart, nEnd - nStart );
					
		if( strLine.GetLength() <= 0 )
		{
			// next line
			nStart = nEnd+2;
			*pnPosition = nStart;
			continue;
		}
		
		
		int rPos = strLine.GetLength()-1;
		// skip spaces at the end of line
		while (rPos >= 0 && strLine[rPos] == ' ')
		{
			rPos--;
		}

		// merge multiline commands to one line
		if( (strLine[rPos] == '|') || 
			(strLine[rPos] == ',') )
		{
			// next line
			nStart = nEnd+2;
			continue;
		}

		TCHAR chHelp;
		if( nStart > 0 )
			chHelp = strText[nStart-1];

		// search for first " (is never within "") so even empty strings ("") are found
		nStartQuote = strLine.Find( _T('"') );
		if ( nStartQuote >= 0 )
			nStartQuote++;

		if( nStartQuote == -1  || !MustBeTranslated(strLine, strKeyword) )	// not found
		{
			strLine = "";
			nStart = *pnPosition = nEnd+2;
			continue;
		}
		// find last " and ignore "" (quotation marks within strings)
		nEndQuote = FindQuote( strLine, nStartQuote );
		if( nEndQuote == -1 )
		{
			strLine = "";
			nStart = *pnPosition = nEnd+2;
			continue;
		}

		nStartQuote--;
		chHelp = strText[nStart+nStartQuote];
		strCaption = strLine.Mid( nStartQuote, nEndQuote-nStartQuote );
		*pnPosition += nEndQuote;
		chHelp = strText[*pnPosition];
		return strCaption;
	}
	return ERR_STR;
}

void CLocalizeRCDlg::OnBnClickedOpenini()
{
	if( m_strLangINI.IsEmpty() )
		AfxMessageBox( IDS_ERR_FILENAMEEMPTY );
	else
	{
		// open lang.ini with standard association
		HINSTANCE hInstance = ShellExecute( m_hWnd, _T("open"), m_strLangINI, NULL, NULL, SW_SHOWNORMAL );
		if( (int)hInstance <= 32 )
			ShowError( IDS_ERR_OPENINI, false, (DWORD)hInstance );
	}
}

void CLocalizeRCDlg::OnBnClickedCreateoutput()
{	
	UpdateData( true );
	int nResult = WriteReadIni( false );
	if( nResult == OLDFILEFORMAT )
	{
		AfxMessageBox( IDS_OLDFILEFORMAT );
		return;
	}
	if( nResult <= 0 )
		return; 
	UpdateData( false );

	// create output RC
	if( m_strOutputRC.IsEmpty() )
	{
		AfxMessageBox( IDS_ERR_FILENAMEEMPTY );
		return;
	}
	try
	{
		CFile File( m_strOutputRC, CFILEFLAG_UNICODEHELPER | CFile::modeWrite|CFile::modeCreate );
	#ifdef UNICODE
		// write 0xFF 0xFE
		BYTE fffe[2] = { 0xFF, 0xFE };
		File.Write(fffe, 2);
	#endif
		File.Write( m_strEdit, sizeof(TCHAR) * m_strEdit.GetLength() );
		File.Close();
	}
	catch( CFileException* e )
	{
		TCHAR szCause[255];
		e->GetErrorMessage(szCause, 255);
		AfxMessageBox(szCause);
		e->Delete();
	}

	// copy header and RES-Folder
	if( m_bCopy )
	{
		CString strOutputFolder = GetFolder( m_strOutputRC );
		CString strInputFolder = GetFolder( m_strInputRC );
        		
		// copy header
		if( !CopyFile( strInputFolder+_T("resource.h"), strOutputFolder+_T("resource.h"), false ) )
			ShowError( IDS_ERR_FILECOPY, true );
		
		// create folder
		if( !CreateDirectory( strOutputFolder+_T("RES"), NULL ) )
		{
			DWORD dwErrCode = GetLastError();
			if( dwErrCode != ERROR_ALREADY_EXISTS )
			{
				ShowError( IDS_ERR_FOLDERCREATE, false, dwErrCode );
				return;
			}
		}

		CFileFind FFind;
		BOOL bWorking = FFind.FindFile( strInputFolder+_T("RES\\*.*"), 0 );
		while( bWorking ) 
		{
			bWorking = FFind.FindNextFile();

			// skip . and .. files; otherwise, we'd
			// recur infinitely!

			if (FFind.IsDots() || FFind.IsDirectory())
				continue;

			else
			{
				// copy file
				if( !CopyFile( FFind.GetFilePath(), strOutputFolder+_T("RES\\")+FFind.GetFileName(), false ) )
				{
					ShowError( IDS_ERR_FILECOPY, true );
				}
			}
		}
		FFind.Close();
	}
	// Fill Edit with InputRC again
	OpenInputRC();
}


// checks if line contents strings that have to be translated
bool CLocalizeRCDlg::MustBeTranslated(CString strLine, CString strKeyword )
{
	// if it is stringtable -> translate
	if( strKeyword == ::strKeyword[5] )
		return true;

	bool bTranslate = true;

	strLine.TrimLeft(_T(" "));
	strLine.TrimRight(_T(" "));
		
	if(strLine[0] == '#') // Preprocessor line
		bTranslate = false;

	if(strLine[0] == '/') // Comment line
		bTranslate = false;

	//--- Exclude following controls from translation ---------------------
	if(strLine.Find(_T("msctls_updown32"), 0) > 0) // Spin control
		bTranslate = false;

	if(strLine.Find(_T("SysTreeView32"), 0) > 0) // Tree view control
		bTranslate = false;

	if(strLine.Find(_T("msctls_trackbar32"), 0) > 0) // Slider control
		bTranslate = false;

	if(strLine.Find(_T("SysIPAddress32"), 0) > 0) // IP adress
		bTranslate = false;

	if(strLine.Find(_T("msctls_hotkey32"), 0) > 0) // Hot key
		bTranslate = false;

	if(strLine.Find(_T("SysListView32"), 0) > 0) // List view control
		bTranslate = false;

	if(strLine.Find(_T("SysAnimate32"), 0) > 0) // Animate control
		bTranslate = false;

	if(strLine.Find(_T("SysMonthCal32"), 0) > 0) // Month calendar
		bTranslate = false;

	if(strLine.Find(_T("ComboBoxEx32"), 0) > 0) // Extended combo box
		bTranslate = false;

	if( !bTranslate )
		return false;

	bTranslate = false;

	//--- Include following controls into translation --------------- 
	if(strLine.Find(_T("CAPTION"), 0) == 0)
		bTranslate = true;					// Dialog box caption

	if(strLine.Find(_T("POPUP"), 0) == 0)
		bTranslate = true;

	if(strLine.Find(_T("MENUITEM"), 0) == 0)
		bTranslate = true;	

	if(strLine.Find(_T("PUSHBUTTON"), 0) == 0)
		bTranslate = true;

	if(strLine.Find(_T("DEFPUSHBUTTON"), 0) == 0)
		bTranslate = true;

	if(strLine.Find(_T("LTEXT"), 0) == 0)
		bTranslate = true;

	if(strLine.Find(_T("RTEXT"), 0) == 0)
		bTranslate = true;

	if(strLine.Find(_T("CTEXT"), 0) == 0)
		bTranslate = true;

	if(strLine.Find(_T("GROUPBOX"), 0) == 0)
		bTranslate = true;

	if(strLine.Find(_T("CONTROL"), 0) == 0)
	{ 
		if(strLine.Find(_T("BS_AUTORADIOBUTTON"), 0) != -1 )
			bTranslate = true;

		if(strLine.Find(_T("BS_AUTOCHECKBOX"), 0) != -1 )
			bTranslate = true;

		if( strLine.Find(_T("Button"), 0) != -1 )
			bTranslate = true;

		// statics with SS_LEFTNOWORDWRAP are controls
		if(strLine.Find(_T("SS_LEFTNOWORDWRAP"), 0) != -1 )
			bTranslate = true;
	}
	if( strLine.Find( _T("ID"), 0) == 0)	// stringtable
		bTranslate = true;
	
	if(strLine.Find( _T("AFX_IDS_"), 0) == 0)
		bTranslate = true;

	if( strLine[0] == '"' )				// accelerator
		bTranslate = true;

    return bTranslate;
}

// return the position where '"' was found
int CLocalizeRCDlg::FindQuote(CString strLine, int nStartPos)
{
	int nPosition = nStartPos;
	
	// search for '"', that is not a quotation mark inside the text ("")
	do 
	{
		nPosition = strLine.Find( '"', nPosition );
		// no '"' was found
		if( nPosition == -1 )
			return nPosition;

		nPosition++;

		if( nPosition >= strLine.GetLength()-1 )
			return nPosition;
	}
	while( strLine[nPosition++] == '"' );

	return nPosition-1;
}

#define PREFIX_CHANGEDITEM _T("*")

int CLocalizeRCDlg::WriteReadIni(bool bWrite)
{
	BYTE nKeyword;
	
	if( m_strLangINI.IsEmpty() )
	{
		AfxMessageBox( IDS_ERR_FILENAMEEMPTY );
		return false;
	}

	// Create or Open INI-File
	CIniEx IniEx;
	if( !IniEx.Open( m_strLangINI ) )
		return false;

	// old sectionnames in INI file ?
	LPCTSTR strOldKeywords[5] =
	{
		_T(" ACCELERATORS "),
		_T(" DIALOG "),
		_T(" DIALOGEX "),
		_T(" MENU "),
		_T(" MENUEX ")
	};

	for( nKeyword = 0; nKeyword < 5; nKeyword++ )
	{
		CString strHelp = strOldKeywords[nKeyword];
		if( IniEx.LookupSection(&strHelp) != -1 )
			return OLDFILEFORMAT;
	}

	CString strCaption, strHelp, strValue, strIDC;
	int nStart, nHelp, nPosition, nSelStart, nSelEnd, nOldLength, nNewLength, nPrevHelp;

	for( nKeyword = 0; nKeyword < NUMKEYWORDS-1; nKeyword++ )
	{
		nPosition = 0;
		// search for keyword section in RC-file
		while( (nPosition = FindSeperateWord( m_strEdit, strKeyword[nKeyword], nPosition )) != -1 )
		{
			nStart = nPosition;
			
			// find BEGIN
			nHelp = FindSeperateWord( m_strEdit, _T("BEGIN"), nPosition );
			
			// find related END (ignore interlocked BEGINs-ENDs)
			nPosition = nHelp-1;
			do
			{
				nPosition = FindSeperateWord( m_strEdit, _T("END"), nPosition+1);
				nHelp = FindSeperateWord( m_strEdit, _T("BEGIN"), nHelp+1 );
			}
			while( nHelp < nPosition && nHelp != -1 );
			
			// save SECTION in strHelp
			strHelp = m_strEdit.Mid( nStart, nPosition-nStart );
			
			nHelp = nPrevHelp = 0;

			// extract caption
			while( (strCaption = ExtractCaption( strHelp, &nHelp, strKeyword[nKeyword], strIDC )) != ERR_STR )
			{
				if( bWrite )
				{
					// check if key in Lang.INI already exists
					strValue = IniEx.GetValue( strKeyword[nKeyword], strCaption );
					if( strValue.IsEmpty() )
					{
						// insert line in Lang.INI
						IniEx.SetValue( strKeyword[nKeyword], strCaption, PREFIX_CHANGEDITEM+strCaption );
					}
					else
					{
						// insert line in Lang.INI
						IniEx.SetValue( strKeyword[nKeyword], strCaption, PREFIX_CHANGEDITEM+strValue );
					}
				}
				else
				{
					// check if key in Lang.INI exists
					strValue = IniEx.GetValue( strKeyword[nKeyword], strCaption );
					if( !strValue.IsEmpty() )
					{
						// if it is DLGINIT
						if( nKeyword == 6 )
						{
							CString strInsert, strToken, strHelp2;
							WORD wData;
							int n;
							int nTokenPos = 0;

							while( (strToken = StringTokenize( strValue, _T(";"), &nTokenPos )) != "" )
							{
								// first line: header
								strHelp2.Format(_T("\t%s, 0x403, %d, 0\r\n"), strIDC, strToken.GetLength()+1 );
								strInsert += strHelp2;

								// second line: data
								for( n=0; n < strToken.GetLength(); n+=2 )
								{
									if( n == (strToken.GetLength() - 1) )
										wData = MAKEWORD( strToken.GetAt(n), 0 );
									else
										wData = MAKEWORD( strToken.GetAt(n), strToken.GetAt(n+1) );
									strHelp2.Format( _T("0x%04x, "), wData );
									strInsert += strHelp2;
								}

								// eventually add null character
								if( n == strToken.GetLength() )
								{
									strInsert += _T("\"\\000\"");
								}
								strInsert += _T("\r\n");
							}
							// only add 0 if DLGINIT block is at the end
							if( nHelp >= strHelp.GetLength() -1 )
							{
								strInsert += _T("\t0\r\n");
								nSelEnd = strHelp.GetLength();
							}
							else
								nSelEnd = nHelp;

							// find BEGIN
							if( nPrevHelp == 0 )
							{
								nSelStart = FindSeperateWord(strHelp, _T("BEGIN"), 0 );
								nSelStart += 7;
							}
							else
								nSelStart = nPrevHelp;

							nOldLength = nSelEnd-nSelStart;
							strHelp.Delete( nSelStart, nOldLength  );
							nNewLength = strInsert.GetLength();
							strHelp.Insert( nSelStart, strInsert );	
						}
						else
						{
							nSelStart = nHelp-strCaption.GetLength();
							nSelEnd = nHelp;

							nOldLength = nSelEnd-nSelStart;
							strHelp.Delete( nSelStart, nOldLength  );
							nNewLength = strValue.GetLength();
							strHelp.Insert( nSelStart, strValue );	
						}
						nHelp += nNewLength - nOldLength;
						if( nHelp < 0 )
							nHelp = 0;
					}
					nPrevHelp = nHelp;
				}
				strIDC.Empty();
			}
			if( !bWrite )
			{
				m_strEdit.Delete( nStart, nPosition-nStart );
				m_strEdit.Insert( nStart, strHelp );
				nPosition = nStart + strHelp.GetLength();
			}
		}
	}
#define CODEPAGE _T("code_page")
	// search for codepage
	////////////////////////////////////////////
	nPosition = 0;
	CString strCodepage;
	while( (nPosition = FindSeperateWord( m_strEdit, CODEPAGE, nPosition )) != -1 )
	{
		nSelEnd = m_strEdit.Find( ')', nPosition );
		nSelStart = m_strEdit.Find( '(', nPosition ) + 1;

		strCaption = m_strEdit.Mid( nSelStart, nSelEnd-nSelStart );
		
		// check if key in Lang.INI already exists
		strValue = IniEx.GetValue( CODEPAGE, strCaption );

		if( bWrite )
		{
	
			if( strValue.IsEmpty() )
			{
				// insert line in Lang.INI
				IniEx.SetValue( CODEPAGE, strCaption, PREFIX_CHANGEDITEM+strCaption );
			}
			else
			{
				// insert line in Lang.INI
				IniEx.SetValue( CODEPAGE, strCaption, PREFIX_CHANGEDITEM+strValue );
			}
		}
		else
		{
			if( !strValue.IsEmpty() )
			{
				m_strEdit.Delete( nSelStart, nSelEnd-nSelStart );
				m_strEdit.Insert( nSelStart, strValue );
				nPosition = nSelStart + strValue.GetLength();
			}
		}
		nPosition++;
	}

	if( bWrite )
	{
		// remove unnecessary lines in Lang.INI
		CStringArray strSections, strKeys;
		IniEx.GetSections( strSections );

		// go through every section
		int nSection=0;
		while( nSection < strSections.GetSize() )
		{
			strKeys.RemoveAll();
			IniEx.GetKeysInSection( strSections[nSection], strKeys );
			for( nKeyword = 0; nKeyword < NUMKEYWORDS; nKeyword++ )
			{
				// dont touch unknown sections
				if( _tcscmp(strSections[nSection], strKeyword[nKeyword]) != 0 )
					continue;

				// Go through every key in this section
				int nKey=0;
				while( nKey < strKeys.GetSize() )
				{
					// check if key is necessary for this RC
					strValue = IniEx.GetValue( strSections[nSection], strKeys[nKey] );
					if( strValue[0] == '*' )
					{
						strValue.TrimLeft( _T(" *") );
						IniEx.SetValue( strSections[nSection], strKeys[nKey], strValue );
					}
					else
					{
						// key is obsolete and no longer needed
						switch( m_nObsoleteItems )
						{
							case 0:	// delete item	
								IniEx.RemoveKey( strSections[nSection], strKeys[nKey]);
								break;
							case 1:
								if( strValue[0] != '#' )
									IniEx.SetValue( strSections[nSection], strKeys[nKey], _T("#") + strValue );
								break;
						}
					}
					nKey++;
				}
				break;
			}
			if( nKeyword == NUMKEYWORDS && strSections[nSection] != CODEPAGE  )
				IniEx.SetValue( strSections[nSection], _T("!!! This sectionname isn't recognized by LocalizeRC. Probably you can delete the whole section !!!"), _T("") );

			strKeys.RemoveAll();
			IniEx.GetKeysInSection( strSections[nSection], strKeys );
			
			// remove empty sections
			if( strKeys.GetSize() == 0 )
			{
				if( IniEx.RemoveSection( strSections[nSection] ) )
					strSections.RemoveAt( nSection );
				else
					nSection++;
			}
			else
				nSection++;
		}
		if( !m_bNoSort )
			IniEx.SortIniValues();
		// save changes
		IniEx.WriteFile();
	}
	return true;
}

void CLocalizeRCDlg::OnDestroy()
{
	CDialog::OnDestroy();
	DestroyIcon( hSmallIcon );
	DestroyIcon( hLargeIcon );

	AfxGetApp()->WriteProfileString( SEC_LASTPROJECT, ENT_WORKSPACE, m_strWorkspace );
	SaveWorkspace();
}	

CString CLocalizeRCDlg::GetFolder(CString strPath)
{
	TCHAR path[_MAX_PATH];
	TCHAR drive[_MAX_DRIVE];   
	TCHAR dir[_MAX_DIR];

	_tcscpy( path, strPath );
	
	// Trenne Pfad von Anwendungsnamen
	_tsplitpath( path, drive, dir, NULL, NULL );

	CString strReturn;
	strReturn.Format( _T("%s%s"), drive, dir);
	return strReturn;
}

void CLocalizeRCDlg::OnBnClickedReverseini()
{
	// Create or Open INI-File
	CIniEx IniEx;
	IniEx.Open( m_strLangINI );

	CStringArray strSections, strKeys;
	CString strHelp;
	IniEx.GetSections( strSections );
	int nKey, nSec;
	
	// go through every section
	for( nSec=0; nSec < strSections.GetSize(); nSec++ )
	{
		// go through every key
		IniEx.GetKeysInSection( strSections[nSec], strKeys );
		for( nKey=0; nKey < strKeys.GetSize(); nKey++ )
		{
			strHelp = IniEx.GetValue( strSections[nSec], strKeys[nKey] );	
			if( strHelp != strKeys[nKey] )
			{
				IniEx.SetValue( strSections[nSec], strHelp, strKeys[nKey] );
				IniEx.RemoveKey( strSections[nSec], strKeys[nKey] );
			}
		}
	}
	// save changes
	IniEx.WriteFile();
}

void CLocalizeRCDlg::OnBnClickedNewWorkspace()
{
	if( OpenSaveDialog(false, false, IDS_EXTLWS, IDS_EXTLWSDESCRIPTION, m_strWorkspace, _T("")) )
	{
		m_strLangINI = "";
		m_strInputRC = "";
		m_strOutputRC = "";
		UpdateData( false );
	}
}

void CLocalizeRCDlg::OnBnClickedChngWorkspace()
{
	if( OpenSaveDialog(true, false, IDS_EXTLWS, IDS_EXTLWSDESCRIPTION, m_strWorkspace, _T("")) )
	{
		if( !LoadWorkspace() )
			AfxMessageBox( IDS_ERR_OPENWORKSPACE );
	}
}

BOOL CLocalizeRCDlg::LoadWorkspace(BOOL bShowError)
{
	CIniEx IniEx;
	if( !IniEx.Open( m_strWorkspace, 1, 0 ) )
		return false;

	CString strPath = GetFolder(m_strWorkspace);
	m_strInputRC = GetAbsolutePathFromIni( &IniEx, ENT_INPUTRC, strPath );
	m_strLangINI = GetAbsolutePathFromIni( &IniEx, ENT_LANGINI, strPath );
	m_strOutputRC = GetAbsolutePathFromIni( &IniEx, ENT_OUTPUTRC, strPath );
	m_bCopy = _ttoi( IniEx.GetValue(ENT_COPY) );
	m_nObsoleteItems = _ttoi( IniEx.GetValue(ENT_OBSITEMS) );
	m_bNoSort = _ttoi( IniEx.GetValue(ENT_NOSORT) );
	OpenInputRC(bShowError);

	UpdateData( false );
	return true;
}

BOOL CLocalizeRCDlg::SaveWorkspace(void)
{
	CIniEx IniEx;
	if( !IniEx.Open( m_strWorkspace ) )
		return false;

	// save settings to Workspace
	CString strHelp;
	strHelp.Format( _T("%d"), m_bCopy );
	IniEx.SetValue( ENT_COPY, strHelp );
	strHelp.Format( _T("%d"), m_nObsoleteItems );
	IniEx.SetValue( ENT_OBSITEMS, strHelp );
	strHelp.Format( _T("%d"), m_bNoSort );
	IniEx.SetValue( ENT_NOSORT, strHelp );
	
	// save changes
	IniEx.WriteFile();

	return 0;
}

CString CLocalizeRCDlg::GetAbsolutePathFromIni(CIniEx* pIniEx, CString strKey, CString strPath)
{
	CString strHelp = pIniEx->GetValue( strKey );
	CString strValue;
	if( strHelp.IsEmpty() )
		return _T("");

	if( PathIsRelative( strHelp ) )
	{
		TCHAR szPath[MAX_PATH];
		PathCombine( szPath, strPath, strHelp );
		strHelp.Format(_T("%s"), szPath);
		return strHelp;
	}
	return strHelp;
}

void CLocalizeRCDlg::OnCbnSelchangeLanguage()
{
	// Save Changes
	AfxGetApp()->WriteProfileInt( SEC_LASTPROJECT, ENT_LANGID, m_CtrlLanguage.GetItemData(m_CtrlLanguage.GetCurSel()) );
	AfxMessageBox( IDS_RESTARTAPP );
}

#define STR_LEN 64

int CLocalizeRCDlg::AddLanguage(CComboBox* pComboBox, LPCTSTR strLangCode, LANGID SelectedID)
{
	LCID lcID;
	TCHAR szLangName[STR_LEN];
	LPTSTR pHelp;
	int nIndex;

	lcID =  MAKELCID( MAKELANGID(_tcstoul( strLangCode, &pHelp, 16 ), SUBLANG_NEUTRAL ), SORT_DEFAULT );
	GetLocaleInfo( lcID, LOCALE_SNATIVELANGNAME , szLangName, STR_LEN);
	nIndex = pComboBox->AddString( szLangName );
	pComboBox->SetItemData( nIndex, lcID );
	if( lcID == SelectedID )
		pComboBox->SetCurSel( nIndex );
	return nIndex;
}

CString CLocalizeRCDlg::StringTokenize(CString strSource, LPCTSTR pszTokens, int* pnStart)
{
#if _MFC_VER >= 0x0700
	return strSource.Tokenize( pszTokens, *pnStart );
#else
	// original code CString::Tokenize from MFC7
	/////////////////////////////////////////////////

	if( pszTokens == NULL )
	{
		return( strSource );
	}

	// (LPCSTR)(LPCTSTR) hack for VC6.
	LPCTSTR pszPlace = (LPCTSTR)strSource+*pnStart;
	LPCTSTR pszEnd = (LPCTSTR)strSource+strSource.GetLength();
	if( pszPlace < pszEnd )
	{
		int nIncluding = StringSpanIncluding( pszPlace, pszTokens );

		if( (pszPlace+nIncluding) < pszEnd )
		{
			pszPlace += nIncluding;
			int nExcluding = StringSpanExcluding( pszPlace, pszTokens );

			int iFrom = *pnStart+nIncluding;
			int nUntil = nExcluding;
			*pnStart = iFrom+nUntil+1;

			return( strSource.Mid( iFrom, nUntil ) );
		}
	}

	// return empty string, done tokenizing
	*pnStart = -1;

	return( CString( "" ) );
#endif
}

#define SEPERATORS _T(" \r\n,\t()")

int CLocalizeRCDlg::FindSeperateWord(CString strText, LPCTSTR strWord, int nStartPos)
{
	int nFoundPos;
	while( (nFoundPos = strText.Find( strWord, nStartPos )) != -1 )
	{
        CString strSeperator;

		// look for preceding character
		if( nFoundPos > 0 )
		{
			strSeperator = strText[nFoundPos-1];
			// if preceding character isn't a separator, continue search
			if( strSeperator.FindOneOf( SEPERATORS ) == -1 )
			{
				strSeperator = strText.Mid( nFoundPos-1, nFoundPos + 40 );
				
				nStartPos = nFoundPos+1;
				continue;
			}
		}

		// look for successing character
		int nSuccessingPos =  nFoundPos+_tcslen(strWord);
		if( nSuccessingPos < strText.GetLength() )
		{
			strSeperator = strText[nSuccessingPos];

			// if successing character isn't a separator, continue search
			if( strSeperator.FindOneOf( SEPERATORS ) == -1 )
			{
				nStartPos = nFoundPos+1;
				continue;
			}
		}
		break;
		
	}
	return nFoundPos;
}

void CLocalizeRCDlg::OnCbnSelchangeObsItems()
{
	UpdateData( true );
}

void CLocalizeRCDlg::OnBnKillfocusCopy()
{
	UpdateData( true );
}

void CLocalizeRCDlg::OnBnKillfocusNosort()
{
	UpdateData( true );
}

void CLocalizeRCDlg::OnBnClickedChngInputrc()
{
	// Change Input RC
	if( OpenSaveDialog( true, true, IDS_EXTRC, IDS_EXTRCDESCRIPTION, m_strInputRC, ENT_INPUTRC ) )
		OpenInputRC();
}

void CLocalizeRCDlg::OnBnClickedChngLangini()
{
	// Change Lang INI
	OpenSaveDialog( false, true, IDS_EXTINI, IDS_EXTINIDESCRIPTION, m_strLangINI, ENT_LANGINI );	
}

void CLocalizeRCDlg::OnBnClickedChngOutputrc()
{
	// Change Output RC
	OpenSaveDialog( false, true, IDS_EXTRC, IDS_EXTRCDESCRIPTION, m_strOutputRC, ENT_OUTPUTRC );
}

BOOL CLocalizeRCDlg::OpenSaveDialog(BOOL bOpen, BOOL bRelative, UINT nExtID, UINT nExtDescriptionID, CString& strEdit, CString strIniEntry)
{
	CString strExtension, strExtensionInfo;
	strExtension.LoadString( nExtID );
	strExtensionInfo.LoadString( nExtDescriptionID ); 

	DWORD dwFlags = OFN_HIDEREADONLY|OFN_ENABLESIZING|OFN_EXPLORER;
	if( bOpen )
		dwFlags |= OFN_FILEMUSTEXIST;
	else
		dwFlags |= OFN_OVERWRITEPROMPT;

#if _MFC_VER >= 0x0700
	CFileDialog FileDialog( bOpen, strExtension, NULL, dwFlags,	strExtensionInfo, this, 0 );
#else
	CFileDialog FileDialog( bOpen, strExtension, NULL, dwFlags,	strExtensionInfo, this );
#endif
	_tcscpy( FileDialog.m_ofn.lpstrFile, strEdit );

	if( FileDialog.DoModal() == IDOK )
	{
		if( bRelative )
		{
			// change to relative path
			TCHAR szOut[MAX_PATH] = _T("");
			if( !PathRelativePathTo( szOut, m_strWorkspace, FILE_ATTRIBUTE_NORMAL, FileDialog.GetPathName(), FILE_ATTRIBUTE_NORMAL ) )
				_tcscpy( szOut, FileDialog.GetFileName() );
			if( strIniEntry )
			{
				CIniEx IniEx;
				
				if( !IniEx.Open( m_strWorkspace ) )
					return false;
				
				CString strValue = szOut;
				IniEx.SetValue( strIniEntry, strValue );
				IniEx.WriteFile();
			}
		}
		strEdit = FileDialog.GetPathName();

		UpdateData( false );
		return true;
	}
	
	return false;
}
BOOL CLocalizeRCDlg::ShowError(UINT nIDString1, bool bGetLastError, DWORD dwErrCode )
{
	CString strString1, strLastError;
	
	if( dwErrCode == 0 )
		dwErrCode = GetLastError();

	strString1.LoadString( nIDString1 );
	if( bGetLastError )	
	{
		strLastError = ConvertErrorToString( dwErrCode );
		strString1 += _T(" ") + strLastError;
	}
	AfxMessageBox( strString1 );
	return true;
}


#include <lmerr.h>
CString CLocalizeRCDlg::ConvertErrorToString( DWORD dwErrCode )
{	
	HMODULE 	hModule = NULL;		// default to system source
	LPTSTR		MessageBuffer = NULL;
	DWORD		dwBufferLength;
	CString		strError;
 
	// Always start off with an empty string
	strError.Empty();

   	// if error_code is in the network range, load the message source
	if (dwErrCode >= NERR_BASE && dwErrCode <= MAX_NERR) 
	{
	    hModule = ::LoadLibraryEx( _TEXT("netmsg.dll"), NULL, LOAD_LIBRARY_AS_DATAFILE );
	}
 
	
	// call FormatMessage() to allow for message text to be acquired
	// from the system or the supplied module handle
	dwBufferLength = ::FormatMessage( 
	    FORMAT_MESSAGE_ALLOCATE_BUFFER |
	    FORMAT_MESSAGE_IGNORE_INSERTS |
	    FORMAT_MESSAGE_FROM_SYSTEM |		// always consider system table
	    ((hModule != NULL) ? FORMAT_MESSAGE_FROM_HMODULE : 0),
	    hModule,					// module to get message from (NULL == system)
	    dwErrCode,
	    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),	// default language
	    (LPTSTR) &MessageBuffer, 0, NULL );

    if ( MessageBuffer ) 
	{
	    if ( dwBufferLength )
			strError = (LPCTSTR)MessageBuffer;
 
	    // free the buffer allocated by the system
	    ::LocalFree(MessageBuffer);
	}
 
	// if you loaded a message source, unload it
	if (hModule != NULL)
	    ::FreeLibrary(hModule);

    if ( strError.GetLength() == 0 )
	    strError.Format( IDS_ERR_UNKNOWN, dwErrCode );
	
	return strError;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
Web Developer
Germany Germany
Author of the shareware WinCD.

Comments and Discussions