An Internationalized Software Project With Auto Tools
Prev Using wxWidgets Next

Using wxWidgets

In this chapter, testproj will be ported to wxWidgets. The result application is a main windows, containing the "right, left" and "right, wrong" text, to demonstrate late translations and ambigous translations. Two menu items open the documentation and show the overview and the introduction. A third menu item opens a dialog and allows the user to enter a number. This number is inserted into a message, demonstrating multi plural handling. A help button in this dialog opens the third page of the documentation.

To create the wxWidgets test program first all no longer used files are deleted. The new test program also has sources in a sub directory, which is this time called "gui". All other source files are recreated as well:
# rm src/main.cpp
# rm src/i18n.cpp
# rm src/i18n.h
# rm src/testproj.h
# rm -r src/testmodule
# mkdir src/gui
# touch src/main.cpp
# touch src/i18n.cpp
# touch src/i18n.h
# touch src/gui/testproj_app.h
# touch src/gui/testproj_app.cpp
# touch src/gui/testproj_frame.h
# touch src/gui/testproj_frame.cpp
# touch src/gui/testproj_dlg.h
# touch src/gui/testproj_dlg.cpp
# touch src/gui/Makefile.am
configure.ac needs to know the new directory.

configure.ac

...

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.
AC_CONFIG_FILES([Makefile src/Makefile src/gui/Makefile po/Makefile.in m4/Makefile \
	doc/Makefile doc/de/Makefile])
AC_OUTPUT
Next, the Makefile in src must point to it as well. Furthermore, the compiler switches to point to the localization and the documentation are no longer needed, as wxWidgets handles the localization and the "gui" module handles the documentation.

src/Makefile.am

SUBDIRS = gui

bin_PROGRAMS = testproj
testproj_SOURCES = main.cpp i18n.cpp
noinst_HEADERS = testproj.h i18n.h
testproj_LDADD = $(top_srcdir)/src/gui/libgui.a
testproj_LDFLAGS = $(LTLIBINTL)

localedir = $(datadir)/locale
DEFS = -DLOCALEDIR=\"$(localedir)\" -DHELPDIR=\"$(helpdir)\" @DEFS@
The wxWidgets help system requires the .hhp, .hhk and .hhc files installed as well:

src/doc/Makefile.am

...

htmlhelp_DATA = $(pictures) $(TESTPROJ_DOCFILES)$(htmlhelp_created_files)

...

src/doc/Makefile.lang.am

...

htmlhelp_DATA = $(pictures) $(TESTPROJ_DOCFILES)$(htmlhelp_created_files)

...
The i18n source is no longer necessary, as wxWidgets supplies a corresponding set of macros. A self-made i18n is necessary only, if own macros should be used or if ambigous translations should be handled. Both will be demonstrated here:

src/i18n.h

#include <wx/intl.h>
#define i18n(x) sgettext(_T(x))
#define i18nTranslate(x) sgettext(x)
#define i18nP(singular, plural, n) wxPLURAL(singular, plural, n)
#define i18nM(x) _T(x)

const wxChar * sgettext (const wxChar * msgid);
int strcntchar(const wxChar * s, wxChar c);
The main difference to the previous version of i18n.h is the different include (this time from the xwWidgets library) and the different character type: wxChar instead of char. This ensures, that the program will compile with or without unicode support.

The implementation therefore uses a portable strchr version. Furthermore wxGetTranslation is called to translate a string, instead of calling a gettext directly:

src/i18n.cpp

#include "i18n.h"

int strcntchar(const wxChar * s, wxChar c)
{
	for (int i = 0; ; ++s, ++i)
	{
		if (!(s = wxStrchr(s, c)))
			return i;
	}
}

const wxChar * sgettext (const wxChar * msgid)
{
	const wxChar * msgval = wxGetTranslation(msgid);
	int pipeCount = strcntchar(msgid, '|');
	if (pipeCount && pipeCount == strcntchar(msgval, '|'))
		msgval = wxStrchr(msgval, '|') + 1;
	return msgval;
}
The main program is now quite simple:

src/main.cpp

#include "gui/testproj_app.h"

IMPLEMENT_APP(CTestProjApp);
It just declares through a macro, which class represents the whole application. This application class together with all other gui related classes are located in the gui sub-directory.

The application class CTestProjApp is defined as follows:

src/gui/testproj_app.h

#ifndef TESTPROJ_APP_INCLUDED
#define TESTPROJ_APP_INCLUDED

#include <wx/app.h>
#include "wx/help.h"

class CTestProjFrame;

class CTestProjApp : public wxApp
{
	static CTestProjApp * m_app;
	CTestProjFrame * m_frame;
	wxHelpController * m_helpController;
	wxString m_helpFile, m_language;
	bool m_localeSwitchFound, m_deleteLogger;
	wxLog * m_oldLogger;
	wxString getHelpFile();
public:
	static CTestProjApp * getApplication();
	static wxHelpController * getHelpController();
	CTestProjApp();
	~CTestProjApp();
	virtual bool OnInit();
	void OnInitCmdLine(wxCmdLineParser & parser);
	bool OnCmdLineParsed(wxCmdLineParser & parser);
	bool OnCmdLineError(wxCmdLineParser & parser);
};
#endif
It derives from wxApp and is therefore "the application". app.h has to be included to get the definition of the base class. help.h is neccessary, as wxHelpController is not a class but a marcro, which points to the implementation available on the current platform.

CTestProjApp is a kind of a singleton, so that other classes have easy access to the help system (wxHelpController). The other member variables are required for the command line parsing.

The implementation is quite long and split in the following for convenience:

src/gui/testproj_app.cpp

#include "testproj_app.h"
#include "../i18n.h"

#include <wx/cmdline.h>
#include <wx/cshelp.h>
#include <wx/log.h>
#include <wx/filename.h>
#include <wx/image.h>

#include "testproj_frame.h"

#define HELP_DIRECTORY L"" HELPDIR

CTestProjApp * CTestProjApp::m_app;

wxString CTestProjApp::getHelpFile()
{
	if (m_helpFile.length())
		return m_helpFile;
#ifdef __WXMSW__ 
	wxString filePrefix = wxT("doc");
	filePrefix.Append(wxFileName::GetPathSeparator());
	wxString filePostfix = wxT("testproj.chm");
#else
	wxString filePrefix = HELP_DIRECTORY;
	if (filePrefix.length() > 0 
		&& filePrefix.at(filePrefix.length() - 1) != wxFileName::GetPathSeparator())
			filePrefix.Append(wxFileName::GetPathSeparator());
	wxString filePostfix = wxT("testproj.hhp");
#endif
	wxString s = filePrefix + m_language + wxFileName::GetPathSeparator() + filePostfix;
	if (::wxFileExists(s))
		return s;
	int i = m_language.find('_');
	if (i > 0 && i < (int)m_language.length() - 1)
	{
		wxString lang = m_language.substr(0, i);
		(m_localeSwitchFound?wxLogError:wxLogMessage)
			(i18n("Help file \"%s\" does not exist. Trying language %s instead"), 
			s.c_str(), lang.c_str());
		s = filePrefix + lang + wxFileName::GetPathSeparator() + filePostfix;
		if (::wxFileExists(s))
			return s;
	}
	(m_localeSwitchFound?wxLogError:wxLogMessage)
		(i18n("Help file \"%s\" does not exist. Trying default instead"), s.c_str());
	s = filePrefix + filePostfix;
	if (::wxFileExists(s))
		return s;
	wxLogError(i18n("Default help file \"%s\" does not exist."), s.c_str());
	return wxT("");
}

This part tries to find the documentation files. If m_helpFile is set through a command line switch, it is returned. Otherwise a search path prefix is defined. It is "doc" on windows (where the documentation will be installed in the directory "doc" below the application directory). On linux/unix it is HELP_DIRECTORY, which is set by configure.

Furthermore the file to be searched for is defined. On windows it is a .chm file, which contains the whole help. On linux/unix it is a .hhp file.

The search algorithm places m_language between pre- and postfix. Note the wxFileName::GetPathSeparator(), which returns the correct path separator (slash or backslash) depending on the target platform. m_language is set below and has the form de_DE. The "for" loop removes the "_DE", if the first test failed. Therefore the directories filePrefix/de_DE, filePrefix/de and filePrefix are tried. The steps taken are traced out.

src/gui/testproj_app.cpp

...

CTestProjApp * CTestProjApp::getApplication()
{
	return m_app;
}

wxHelpController * CTestProjApp::getHelpController()
{
	return getApplication()->m_helpController;
}

CTestProjApp::CTestProjApp() : m_helpController(NULL), m_localeSwitchFound(false)
	, m_deleteLogger(false), m_oldLogger(NULL)
{
}

CTestProjApp::~CTestProjApp()
{
	delete m_helpController;
	if (m_deleteLogger)
	{
		delete wxLog::SetActiveTarget(m_oldLogger);
	}
}

bool CTestProjApp::OnInit()
{
	wxLog::SetVerbose(true);
#ifndef __WXMSW__ 
	m_oldLogger = wxLog::SetActiveTarget( new wxLogStderr );
	m_deleteLogger = true;
#endif
	wxImage::AddHandler(new wxPNGHandler());
	m_app = this;
	if (!wxApp::OnInit())
		return false;
	SetVendorName(wxT("J.T. Kirk"));
	wxString helpFile = getHelpFile();
	wxHelpController * helpController = new wxHelpController();
	if (helpFile.length())
		wxLogMessage(i18n("Using help file \"%s\"."), helpFile.c_str());
	if (!helpFile.length() || !helpController->Initialize(helpFile))
	{
		wxLogWarning(wxT("Cannot initialize the help system"));
		delete helpController;
	}
	else
	{
		m_helpController = helpController;
		wxHelpControllerHelpProvider* provider = new wxHelpControllerHelpProvider;
		wxHelpProvider::Set(provider);
		provider->SetHelpController(m_helpController);
	}
	m_frame = new CTestProjFrame(i18n("Test Project"));
	m_frame->Show(TRUE);
	SetTopWindow(m_frame);
	return true;
}
OnInit() is the first method called after starting the program. It makes the tracer verbose, i.e. lets him trace out more. wxWidgets by default traces messages to a trace dialog. This is very anoying, as the user has to close such a dialog permantly (or ignore it). Better is stdout or stderr, which unfortunately do not exist on windows (gui applications). Therefore on all other platforms, the trace target is set to stderr. The current tracer is stored in m_oldLogger for later cleanup in the destructor. For this demo application, the default tracing is acceptable on windows. Real applications should make a different choice here.

The png handler is necessary, as the online documentation contains png pictures. After setting m_app for the "singleton", the application is initialized, which parses the command line arguments. If anything goes wrong, the application stops here.

The next lines initialize the help system, mainly by calling getHelpFile(). The remaining lines are creating the main frame and show it.

src/gui/testproj_app.cpp

...

void CTestProjApp::OnInitCmdLine(wxCmdLineParser & parser)
{
	parser.AddOption(wxT("l"), wxT("language"), i18n("language. Example: de or de_DE"));
#ifdef __WXMSW__ 
	parser.AddOption(wxT("d"), wxT("docfile"), i18n("help file. Should be a .chm file"));
#else
	parser.AddOption(wxT("d"), wxT("docfile"), i18n("help file. Should be a .hpp file"));
#endif
	parser.AddSwitch(wxT("?"), wxT("help"), i18n("show this help"), 
		wxCMD_LINE_OPTION_HELP | wxCMD_LINE_PARAM_OPTIONAL);
}


bool CTestProjApp::OnCmdLineParsed(wxCmdLineParser & parser)
{
	wxLocale::AddCatalogLookupPathPrefix(_T("po"));
	static wxLocale locale;
	wxString language;
	if (parser.Found(wxT("l"), & language))
	{
		m_localeSwitchFound = true;
		m_language = language;
		const wxLanguageInfo * languageInfo = wxLocale::FindLanguageInfo(language);
		if (!languageInfo)
		{
			wxLogError(i18n("Locale (language) \"%s\" does not exist"), language.c_str());
			return false;
		}
		locale.Init(languageInfo->Language);
	}
	else
	{
		locale.Init();
		m_language = locale.GetCanonicalName();
	}
	if (parser.Found(wxT("d"), & m_helpFile))
	{
		if (!::wxFileExists(m_helpFile))
		{
			wxLogError(i18n("Help file \"%s\" does not exist"), m_helpFile.c_str());
			return false;
		}
	}

	wxString canonicalName = locale.GetCanonicalName();
	wxLogMessage(i18n("Using locale (language): %s"), 
		(canonicalName.length()?canonicalName:wxString(i18n("Default"))).c_str());
	if (locale.AddCatalog(wxT("testproj")))
	{
		wxLogMessage(i18n("Locale (language) catalog found"));
	}
	else
	{
		if (language.length())
		{
			wxLogError(i18n("Locale (language) catalog not found"));
			return false;
		}
		wxLogMessage(i18n("Locale (language) catalog not found. Using default"));
	}

	return true;
}

bool CTestProjApp::OnCmdLineError(wxCmdLineParser & parser)
{
	parser.Usage();
	return false;
}
This final block of code does the command line parsing. OnInitCmdLine is called first. It defines all allowed command switches. OnCmdLineParsed is called after the command line was parsed. The argument allows to access the command line switches detected by the parser.

The first few lines initialize wxLocale, which essentailly is a wrapper around gettext. The added lookup path prefix is for windows, as here the .mo files are located in a directory "po" below the application directory. On linux/unix the standard gettext .mo directory is added automatically.

If a specific language is passed with the -l switch, FindLanguageInfo() is used to check, if the language code is valid. If so wxLocale::Init sets gettext to this language. If no language is given, wxLocale::Init without an argument sets the language according to the environment or the windows installation. In any case the variable m_language will be set with the choosen language code, so that getHelpFile() above can find the correct documentation files.

If the -d switch is given, the provided path to the help file is stored in m_helpFile and used in getHelpFile(). Afterwards wxLocale::AddCatalog is used to actually load the correct testproj.mo file, which might succeed or not.

OnCmdLineError finally is called, if the command line was invalid. In this case all switches are printed out and the application terminates.

In summary CTestProjApp does the command line parsing. Depending on its result, gettext and the help system are initialized. On windows, all necessary files are below the application directory. On unix/linux they are on the "usual locations". If no switch is provided, the default language is used, which is controlled by environment variables on unix/linux and on the installation on windows.

Finally the frame is created, which is the "main window". It contains a menu, a status bar and displays some text in its main area:

src/gui/testproj_frame.h

#ifndef TESTPROJ_FRAME_INCLUDED
#define TESTPROJ_FRAME_INCLUDED

#include <wx/frame.h>

class wxCommandEvent;

class CTestProjFrame : public wxFrame
{
	enum CTestProjFrameWindowIds {openDialogId = wxID_HIGHEST + 1, helpContentsId, helpPrefaceId};
public:
	CTestProjFrame(const wxString & title);
	void OnHelpContents(wxCommandEvent & event);
	void OnHelpPreface(wxCommandEvent & event);
	void OnOpenDialog(wxCommandEvent & event);
	DECLARE_EVENT_TABLE()
};

#endif
CTestProjFrame derives from wxFrame, which makes it a frame. The enum generates some ids with a numerical values above wxID_HIGHEST. These ids will be attached to menu items. The DECLARE_EVENT_TABLE macro allows to attach these ids with the On-methods, so that clicking a menu item will call thses methods.

The implementation is also split for convenience:

src/gui/testproj_frame.cpp

#include "testproj_frame.h"
#include "../i18n.h"

#include <wx/menu.h>
#include <wx/sizer.h>
#include <wx/textctrl.h>
#include <wx/stattext.h>
#include <wx/settings.h>

#include "testproj_app.h"
#include "testproj_dlg.h"

// These arrays will be instantiated before gettext is initialized. 
// Nevertheless they will be translated correctly
// The string 'right' has two different meanings and is prefixed, so that it
// can have different translations.
const wxChar * directions [] = {i18nM("directions|right"), i18nM("left")};
const wxChar * results [] = {i18nM("results|right"), i18nM("wrong")};
In this example amgious strings and late translations are demonstrated as well. The two arrays contain strings, which can not be translated during the creation, as they are created during the loading of the program, while the translation language is calculated later. Therefore they are just marked for translation by the i18nM macro. These strings will be passed to i18nTranslate from i18n.h to translate them before they are displayed.

Furthermore "right" is an ambigous string and has a prefix to distinguish the meanings. i18nTranslate will strip off the prefix.

src/gui/testproj_frame.cpp

...

CTestProjFrame::CTestProjFrame(const wxString & title) : wxFrame(NULL, wxID_ANY, title)
{
	wxMenu *menuOpen = new wxMenu;
	menuOpen->Append(openDialogId, i18n("&Dialog"), i18n("Open a dialog"));
	wxMenu *menuHelp = new wxMenu;
	menuHelp->Append(helpContentsId, i18n("&Contents"), i18n("Show the contents of the documentation"));
	menuHelp->Append(helpPrefaceId, i18n("&Preface"), i18n("Show the preface of the documentation"));
	wxMenuBar *menuBar = new wxMenuBar;
	menuBar->Append(menuOpen, i18n("&Open"));
	menuBar->Append(menuHelp, i18n("&Help"));
	SetMenuBar( menuBar );
	CreateStatusBar();
	SetStatusText(i18n("Welcome to TestProj!"));

	wxString s;
	s.Printf(_T("%d"), sizeof(wxChar));
	s = i18n("Sizeof(wxChar): ") + s + _T("\n");
	s += i18n("Possible directions: ");
	s += i18nTranslate(directions[0]);
	s += _T(", ");
	s += i18nTranslate(directions[1]);
	s += _T("\n");
	s += i18n("Possible results: ");
	s += i18nTranslate(results[0]);
	s += _T(", ");
	s += i18nTranslate(results[1]);

	wxTextCtrl * textCtrl = new wxTextCtrl(this, wxID_ANY, s, wxDefaultPosition, wxDefaultSize, 
		wxTE_MULTILINE | wxTE_READONLY);
	wxBoxSizer * sizer = new wxBoxSizer(wxHORIZONTAL);
	sizer->Add(textCtrl, 1, wxEXPAND, 0);
	sizer->SetMinSize(wxSize(300, 100));
	SetSizer(sizer);
	sizer->SetSizeHints(this);
}
The constructor sets up the frame. It first creates the menu, adds the menu items and assigns an id to each of them. Then the status bar is created and a text is shown there. The string s contains the text to be displayed on the main area. The _T macro just marks strings. On unicode builds, it expands to L, which makes the strings unicode strings. On ansi builds it expands to nothing. All other strings are translated.

To show the string, a text control is used. It has the styles read only and multi line, so that it just displays a text. This text control is added to a sizer. All windows in wxWidgets contain a sizer as its main area. sizers can take gui elements and other sizers. Its purpose is to grow and shrink the gui elements, if the window is resized. As here the sizer just contain one gui element, it scales with the window (wwich is determined by the flag wxEXPAND).

src/gui/testproj_frame.cpp

...

void CTestProjFrame::OnHelpContents(wxCommandEvent & WXUNUSED(event))
{
	if (CTestProjApp::getHelpController())
		CTestProjApp::getHelpController()->DisplayContents();
}

void CTestProjFrame::OnHelpPreface(wxCommandEvent & WXUNUSED(event))
{
	if (CTestProjApp::getHelpController())
		CTestProjApp::getHelpController()->DisplaySection(_T("overview.html"));
}

void CTestProjFrame::OnOpenDialog(wxCommandEvent & event)
{
	CTestProjDlg testProjDlg(this);
	testProjDlg.ShowModal();
}

BEGIN_EVENT_TABLE(CTestProjFrame, wxFrame)
	EVT_MENU(CTestProjFrame::helpContentsId, CTestProjFrame::OnHelpContents)
	EVT_MENU(CTestProjFrame::helpPrefaceId, CTestProjFrame::OnHelpPreface)
	EVT_MENU(CTestProjFrame::openDialogId, CTestProjFrame::OnOpenDialog)
END_EVENT_TABLE()
Here the methods are defined, which are called, if a menu item is clicked. The help menu items check, if a help controller exists (i.e. if the application found the help file). If so, a special .html is shown. This works also in windows, where all .html files are compiled into the .chm file.

If the "open->dialog" menu item was clicked, the CTestProjDlg dialog is opened as a modal dialog. This means, that it has to be closed, before the frame takes any further actions.

The EVENT_TABLE macros map the menu item ids to the methods to call. They require the DECLARE_EVENT_TABLE macro to be present in the header file.

Finally the little test dialog:

src/gui/testproj_dlg.h

#ifndef TESTPROJ_DLG_INCLUDED
#define TESTPROJ_DLG_INCLUDED

#include <wx/dialog.h>

class wxTextCtrl;
class wxStaticText;
class wxCommandEvent;

class CTestProjDlg : public wxDialog
{
	enum CTestProjFrameWindowIds {numberTextCtrlId = wxID_HIGHEST + 1};
	wxTextCtrl * m_numberTextCtrl;
	wxStaticText * m_resultStaticText;
public:
	CTestProjDlg(wxWindow * parent);
	void OnNumberCtrlTextChanged(wxCommandEvent & event);
	void OnHelpButtonClicked(wxCommandEvent & event);
	DECLARE_EVENT_TABLE()
};


#endif
CTestProjDlg derives from wxDialog, which makes it a dialog. It will contain among others a text control and a static text, which will take a message text. The two On-methods are called, if the text in the text control changed or if the help button was pressed.

The implementation again is split for convenience:

src/gui/testproj_dlg.cpp

#include "testproj_dlg.h"
#include "testproj_app.h"
#include "../i18n.h"

#include <wx/sizer.h>
#include <wx/textctrl.h>
#include <wx/stattext.h>

CTestProjDlg::CTestProjDlg(wxWindow * parent) : wxDialog(parent, wxID_ANY, 
	wxString(i18n("A Test Dialog")), wxDefaultPosition, wxDefaultSize, 
	wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
	wxBoxSizer * sizer = new wxBoxSizer(wxVERTICAL);

	wxBoxSizer * inputFieldSizer = new wxBoxSizer(wxHORIZONTAL);
	inputFieldSizer->Add(new wxStaticText(this, wxID_ANY, i18n("&Please enter a number:"))
		, 0, wxALIGN_CENTER_VERTICAL, 3);
	m_numberTextCtrl = new wxTextCtrl(this, numberTextCtrlId, _T(""), wxDefaultPosition
		, wxDefaultSize);
	inputFieldSizer->Add(m_numberTextCtrl, 1, wxALL | wxEXPAND, 3);
	sizer->Add(inputFieldSizer, 0, wxALIGN_TOP | wxALL | wxEXPAND);

	m_resultStaticText = new wxStaticText(this, wxID_ANY, _T(" "));
	sizer->Add(m_resultStaticText, 0, wxALL | wxEXPAND);

	sizer->Add(new wxStaticText(this, wxID_ANY, _T("")), 1, wxEXPAND);

	wxBoxSizer * button_sizer = new wxBoxSizer(wxHORIZONTAL);
	button_sizer->Add(new wxButton(this, wxID_HELP, i18n("&Help")), 0, wxALL, 3);
	wxButton * ok = new wxButton(this, wxID_OK, i18n("&OK"));
	button_sizer->Add(ok, 0, wxALL, 3);
	ok->SetDefault();
	sizer->Add(button_sizer, 0, wxALIGN_BOTTOM | wxALIGN_RIGHT);

	SetSizer(sizer);
	sizer->SetSizeHints(this);
}
The constructor builds up the dialogs. It contains some nested sizer. The main sizer is a wxBoxSizer, which shows all its elements on top of each other (wxVERTICAL). It contains two wxBoxSizer, which show their elements horizontal (wxHORIZONTAL). In betrween there is the static text control. The first horizontal sizer contains a static text and the edit control. The text is shown left of the edit control. The & in the text controls display string underlines the following letter (T in this example) and makes it a short cut to set the focus into the edit control.

Below the input control is another static text control, which will take output of this dialog, Initially it is empty. The lower sizer contains two button. The attached ids are this time wxWidgets default ids, which also cause default action, if the ok button is pressed (the dialog is closed).

The sizing behaviour is controlled by providing or not providing the flag wxEXPAND and by the integer number before this flag, which indicates the ammount of size, the element will take. In this example, the edit control and the static text take all additional horizontal size. The buttons are just shifted.

src/gui/testproj_dlg.cpp

...

void CTestProjDlg::OnNumberCtrlTextChanged(wxCommandEvent & event)
{
	wxString text = m_numberTextCtrl->GetValue();
	long val = -1;
	bool valid = text.ToLong(& val);
	if (val < 0)
		valid = false;
	wxString s;
	if (valid)
		s.Printf(i18nP("You have won one point", "You have won %d points", val), val);
	else
		s = i18n("Error: enter a non negative number!");
	m_resultStaticText->SetLabel(s);
}

void CTestProjDlg::OnHelpButtonClicked(wxCommandEvent & event)
{
	if (CTestProjApp::getHelpController())
		CTestProjApp::getHelpController()->DisplaySection(_T("test_chapter.html"));
}

BEGIN_EVENT_TABLE(CTestProjDlg, wxDialog)
	EVT_TEXT(CTestProjDlg::numberTextCtrlId, CTestProjDlg::OnNumberCtrlTextChanged)
	EVT_BUTTON(wxID_HELP, CTestProjDlg::OnHelpButtonClicked)
END_EVENT_TABLE()
This part shows the "action". At the bottom events are associated with methods. A click on the help button calls OnHelpButtonClicked, which shows a page in the help system. Any key stroke to the text control calls OnNumberCtrlTextChanged, which tries to convert the entered string into a number. The result is shown in the static text control. It also demonstrates multi plural handling.

Finally the Makefile, which compiles these files:

src/gui/Makefile.am

noinst_LIBRARIES = libgui.a
noinst_HEADERS = testproj_app.h testproj_frame.h testproj_dlg.h
libgui_a_SOURCES = testproj_app.cpp testproj_frame.cpp testproj_dlg.cpp

DEFS = -DHELPDIR=\"$(helpdir)\" @DEFS@
The project should now compile without problems:
# gmake

...

# gmake update-po

...

The localization:

po/de.po

...

#: src/gui/testproj_app.cpp:39
#, c-format
msgid "Help file \"%s\" does not exist. Trying language %s instead"
msgstr "Hilfe-Datei \"%s\" existiert nicht. Versuche statt dessen Sprache %s"

#: src/gui/testproj_app.cpp:46
#, c-format
msgid "Help file \"%s\" does not exist. Trying default instead"
msgstr "Hilfe-Datei \"%s\" existiert nicht. Versuche statt dessen Standardsprache"

#: src/gui/testproj_app.cpp:50
#, c-format
msgid "Default help file \"%s\" does not exist."
msgstr "Standard Hilfe-Datei \"%s\" existiert nicht."

#: src/gui/testproj_app.cpp:93
#, c-format
msgid "Using help file \"%s\"."
msgstr "Verwende Hilfe-Datei \"%s\"."

#: src/gui/testproj_app.cpp:106
msgid "Test Project"
msgstr "Test Projekt"

#: src/gui/testproj_app.cpp:114
msgid "language. Example: de or de_DE"
msgstr "Sprache. Beispiel: de oder de_DE"

#: src/gui/testproj_app.cpp:116
msgid "help file. Should be a .chm file"
msgstr "Hilfe-Datei. Sollte eine .chm Datei sein"

#: src/gui/testproj_app.cpp:118
msgid "help file. Should be a .hpp file"
msgstr "Hilfe-Datei. Sollte eine .hpp Datei sein"

#: src/gui/testproj_app.cpp:120
msgid "show this help"
msgstr "Zeige diese Hilfe an"

#: src/gui/testproj_app.cpp:137
#, c-format
msgid "Locale (language) \"%s\" does not exist"
msgstr "\"Locale\" (Sprache) \"%s\" existiert nicht"

#: src/gui/testproj_app.cpp:151
#, c-format
msgid "Help file \"%s\" does not exist"
msgstr "Hilfe-Datei \"%s\" existiert nicht"

#: src/gui/testproj_app.cpp:157
#, c-format
msgid "Using locale (language): %s"
msgstr "Verwende \"Locale\" (Sprache): %s"

#: src/gui/testproj_app.cpp:158
msgid "Default"
msgstr "Standard"

#: src/gui/testproj_app.cpp:161
msgid "Locale (language) catalog found"
msgstr "\"Locale\" (Sprach-) Katalog gefunden"

#: src/gui/testproj_app.cpp:167
msgid "Locale (language) catalog not found"
msgstr "\"Locale\" (Sprach-) Katalog nicht gefunden"

#: src/gui/testproj_app.cpp:170
msgid "Locale (language) catalog not found. Using default"
msgstr "\"Locale\" (Sprach-) Katalog nicht gefunden.Verwende Standardkatalog"

#: src/gui/testproj_dlg.cpp:10
msgid "A Test Dialog"
msgstr "Ein Test Dialog"

#: src/gui/testproj_dlg.cpp:16
#, fuzzy
msgid "&Please enter a number:"
msgstr "&Geben Sie eine Zahl ein:"

#: src/gui/testproj_dlg.cpp:29 src/gui/testproj_frame.cpp:29
msgid "&Help"
msgstr "&Hilfe"

#: src/gui/testproj_dlg.cpp:30
msgid "&OK"
msgstr "&OK"

#: src/gui/testproj_dlg.cpp:48
#, fuzzy, c-format
msgid "You have won one point"
msgid_plural "You have won %d points"
msgstr[0] "Sie haben einen Punkt gewonnen!\n"
msgstr[1] "Sie haben %d Punkte gewonnen!\n"

#: src/gui/testproj_dlg.cpp:50
msgid "Error: enter a non negative number!"
msgstr "Fehler: Geben Sie eine nicht negative Zahl ein!"

#: src/gui/testproj_frame.cpp:17
msgid "directions|right"
msgstr "rechts"

#: src/gui/testproj_frame.cpp:17
msgid "left"
msgstr "links"

#: src/gui/testproj_frame.cpp:18
msgid "results|right"
msgstr "Ergebnisse|richtig"

#: src/gui/testproj_frame.cpp:18
msgid "wrong"
msgstr "falsch"

#: src/gui/testproj_frame.cpp:23
msgid "&Dialog"
msgstr "&Dialog"

#: src/gui/testproj_frame.cpp:23
msgid "Open a dialog"
msgstr "Öffnet einen Dialog"

#: src/gui/testproj_frame.cpp:25
msgid "&Contents"
msgstr "&Inhalt"

#: src/gui/testproj_frame.cpp:25
msgid "Show the contents of the documentation"
msgstr "Zeige das Inhaltsverzeichnis der Dokumentation"

#: src/gui/testproj_frame.cpp:26
msgid "&Preface"
msgstr "&Vorwort"

#: src/gui/testproj_frame.cpp:26
msgid "Show the preface of the documentation"
msgstr "Zeige das Vorwort der Dokumentation"

#: src/gui/testproj_frame.cpp:28
msgid "&Open"
msgstr "Ö&ffne"

#: src/gui/testproj_frame.cpp:32
msgid "Welcome to TestProj!"
msgstr "Willkommen zum Testprojekt"

#: src/gui/testproj_frame.cpp:36
msgid "Sizeof(wxChar): "
msgstr "Sizeof(wxChar): "

#: src/gui/testproj_frame.cpp:37
#, fuzzy
msgid "Possible directions: "
msgstr "Mögliche Richtungen: %s, %s\n"

#: src/gui/testproj_frame.cpp:42
#, fuzzy
msgid "Possible results: "
msgstr "Mögliche Ergebnisse: %s, %s\n"

#~ msgid ""
#~ "\n"
#~ "Help: %s/index.html\n"
#~ msgstr ""
#~ "\n"
#~ "Hilfe: %s/index.html\n"

#~ msgid "Test chapter: %s/test_chapter.html\n"
#~ msgstr "Test Kapitel: %s/test_chapter.html\n"

#~ msgid "Hello world!\n"
#~ msgstr "Hallo Welt!\n"

#~ msgid "Press a key\n"
#~ msgstr "Drücken Sie eine Taste\n"

#~ msgid "Bye\n"
#~ msgstr "Auf Wiedersehen\n"
Finally it can be installed and tested:
# gmake update-gmo
...

37 translated messages.

...

# gmake install

...

# testproj --help
Usage: testproj [-l <str>] [-d <str>] [-?]
  -l, --language=<str>  language. Example: de or de_DE
  -d, --docfile=<str>   help file. Should be a .hpp file
  -?, --help            show this help
# testproj
...

21:18:37: Using locale (language): en_GB
21:18:37: looking for catalog 'testproj' in path 'po/en_GB/LC_MESSAGES:po/en_GB:...
21:18:37: catalog file for domain 'testproj' not found.
21:18:37: Locale (language) catalog found
21:18:37: Help file "/usr/local/share/doc/testproj/en_GB/testproj.hhp" does not exist.
	Trying language en instead
21:18:37: Help file "/usr/local/share/doc/testproj/en/testproj.hhp" does not exist. 
	Trying default instead
21:18:37: Using help file "/usr/local/share/doc/testproj/testproj.hhp".
The application should show its main window (here started from KDE under FreeBSD):



The same with the localized version:
# testproj -l de
...

02:16:49: Using locale (language): de_DE
02:16:49: Suche Nachrichtenkatalog 'testproj' in Pfad 'po/de_DE/LC_MESSAGES:po/de_DE:...
02:16:49: Verwende Nachrichtenkatalog 'testproj' von '/usr/local/share/locale/de/LC_MESSAGES/testproj.mo'.
02:16:49: "Locale" (Sprach-) Katalog gefunden
02:16:49: Verwende Hilfe-Datei "/usr/local/share/doc/testproj/de/testproj.hhp".
Now the main window looks like:



The result source code can be downloaded here.

Next, testproj is built under windows.
Prev Home Next
wxWidgets Overview Compiling under windows