How to extract strings from gui for translation Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
Nico
Knows some wx things
Knows some wx things
Posts: 31
Joined: Sun Nov 07, 2010 5:19 pm

How to extract strings from gui for translation

Post by Nico »

Hello,

I am trying to make a program with text translated to the users language. To get going I figured I should start with a simple hello world program and I got that working in the console, using gettext.
I don't want to make a console program, so I would like to port this idea to a wxWidgets gui. I created the code below and tried to extract the .pot file with the original strings to start translating. As you can see I tried this in several ways, making the code more and more messy, but I have not yet found the correct way.

Is there anybody that can tell me what to change so that I can extract the strings from this code to a .pot file for translation?

Thanks in advance for any help.

Kind regards, Nico


mainApp.h, just to start things

Code: Select all

#ifndef MAINAPP_H
#define MAINAPP_H

#include <wx/intl.h>

#include <locale.h>
#include <stdio.h>
#include <stdlib.h>

#include "window.h"
#include <wx/image.h>
#include <wx/app.h>

class mainApp : public wxApp
{
    public:
        virtual bool OnInit();
    protected:
    private:
};

#endif // MAINAPP_H
mainApp.cpp, just to start things

Code: Select all

#include "mainApp.h"

IMPLEMENT_APP(mainApp);

bool mainApp::OnInit()
{
    bool wxsOK = true;
    wxInitAllImageHandlers();
    if ( wxsOK )
    {
    	window Dlg(0);
    	SetTopWindow(&Dlg);
    	Dlg.ShowModal();
    	wxsOK = false;
    }
    return wxsOK;
}
window.h

Code: Select all

#ifndef WINDOW_H
#define WINDOW_H
#define _(String) gettext(String)
//     #define N_(String) String
//     #define textdomain(Domain)
//     #define bindtextdomain(Package, Directory)
//(*Headers(window)
#include <wx/stattext.h>
#include <wx/dialog.h>

#include "C:\Program Files\GnuWin32\include\libintl.h"
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
//*)

class window: public wxDialog
{
	public:

		window(wxWindow* parent,wxWindowID id=wxID_ANY,const wxPoint& pos=wxDefaultPosition,const wxSize& size=wxDefaultSize);
		virtual ~window();

		//(*Declarations(window)
		wxStaticText* StaticText1;
		//*)

	protected:

		//(*Identifiers(window)
		static const long ID_STATICTEXT1;
		//*)

	private:

		//(*Handlers(window)
		//*)

		DECLARE_EVENT_TABLE()
};

#endif
window.cpp, the file with the strings

Code: Select all

#include "window.h"

//(*InternalHeaders(window)
#include <wx/intl.h>
//#include <wx/string.h>
//*)
#include "C:\Program Files\GnuWin32\include\libintl.h"
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>


//(*IdInit(window)
const long window::ID_STATICTEXT1 = wxNewId();
//*)

BEGIN_EVENT_TABLE(window,wxDialog)
	//(*EventTable(window)
	//*)
END_EVENT_TABLE()


window::window(wxWindow* parent,wxWindowID id,const wxPoint& pos,const wxSize& size)
{
    setlocale( LC_ALL, "" );
    bindtextdomain( "test", "./vertaal/" );
    textdomain( "test" );
	//(*Initialize(window)
	Create(parent, id, _("Test window"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE, _T("id"));
	SetClientSize(wxDefaultSize);
	Move(wxDefaultPosition);
	StaticText1 = new wxStaticText(this, ID_STATICTEXT1, _("Dit is een test om te zien of we dit in runtime kunnen vertalen."), wxPoint(56,152), wxDefaultSize, 0, _T("ID_STATICTEXT1"));
	//*)
}

window::~window()
{
	//(*Destroy(window)
	//*)
}

p.s. Just in case the code is right but my use of it is wrong, to extract te information I use the following command in command prompt:
C:\Program Files\GnuWin32\bin>xgettext -d test -s -o "c:\Documents and Settings\Nico\cpp workspace\TestVertalen\vertaal\test.pot" "c:\Documents and Settings\Nico\cpp workspace\TestVertalen\window.cpp"
francis33y
Experienced Solver
Experienced Solver
Posts: 70
Joined: Wed Feb 13, 2008 9:21 am
Location: Montpellier, France

Post by francis33y »

I think your code is alright (even if some strings are not encapsulated with either _() or _T() ).

The error is probably in the xgettext command line.
Here is what I use on mac:

Code: Select all

CPP_FILE_LIST=`find ./ -name '*.cpp' -print`
xgettext -d dryer -s --keyword=_ -o dryer.po -j $CPP_FILE_LIST
after that I use poedit to create dryer.mo and copy that in the appropriate directory (different from win)

In the program, you need then to init the lang:

Code: Select all

void MyApp::SelectLanguage(int lang)
{
	delete m_locale;
	m_locale = new wxLocale(lang);
//	m_locale->AddCatalogLookupPathPrefix(wxGetCwd());
//	m_locale->AddCatalog(wxT("dryer"));
}

long MyApp::Set_Lang(int lng, bool ask)
{
	if (ask)
	{
		const wxString langs[] =
		{
			_T("(System default)"),
			_T("French"),
			_T("English"),
			_T("Portuguese (Brazilian)"),
		};

	  SetExitOnFrameDelete(FALSE);
	  lng = wxGetSingleChoiceIndex(_T("Please choose language:"),_T("Language"), WXSIZEOF(langs), langs);
	  SetExitOnFrameDelete(TRUE);
	}
	APP_LANG=lng;
  switch (lng)
    {
    case 0 : 
		wxGetApp().SelectLanguage(wxLANGUAGE_DEFAULT);
		break;

    case 1 : 
		wxGetApp().SelectLanguage(wxLANGUAGE_FRENCH);
		break;

    case 3 : 
		wxGetApp().SelectLanguage(wxLANGUAGE_PORTUGUESE_BRAZILIAN);
//		wxGetApp().SelectLanguage(wxLANGUAGE_PORTUGUESE);
		break;

    case -1:
    case 2 : 
		wxGetApp().SelectLanguage(wxLANGUAGE_ENGLISH_US);
		break;
    } 
	return lng;
}
and you need to init all of that

Code: Select all

bool MyApp::OnInit()
{
	m_locale = NULL ;
	SetVendorName(_T("Toto"));
	SetAppName(_T("dryer"));
	wxConfigBase *pConfig = wxConfigBase::Get();
	pConfig->SetRecordDefaults();
	
	if (!pConfig->Read(_T("Lang"),&APP_LANG,-1))
		APP_LANG=Set_Lang(APP_LANG,true);
	else
		APP_LANG=Set_Lang(APP_LANG,false);
	pConfig->Read(_T("Comma"),&APP_WITH_COMMA,false);
    wxString argv0 = wxTheApp->argv[0];
    
    if (wxIsAbsolutePath(argv0))
      m_apppath = argv0;
    else
      {
	wxPathList pathlist;
	pathlist.AddEnvList(wxT("PATH"));
	m_apppath = pathlist.FindAbsoluteValidPath(argv0);
      }
    
    wxFileName filename(m_apppath);
    filename.Normalize();
    m_apppath = filename.GetFullPath();
	m_apppath=wxPathOnly(m_apppath);
#if defined(__WXMAC__)
	m_apppath=GetBundleSharedSupportDir();
#endif
    m_locale->AddCatalogLookupPathPrefix(wxGetCwd());
    m_locale->AddCatalogLookupPathPrefix(m_apppath);
    m_locale->AddCatalogLookupPathPrefix(m_apppath+ wxFILE_SEP_PATH + _T("locale"));
   if (!m_locale->AddCatalog(_T("dryer")))
	{
		wxMessageBox( _T("Couldn't find a catalog for current language"),_T("Info"), wxOK | wxICON_INFORMATION );
		//return false;
	}

....

Why don't you use poedit ?
Auria
Site Admin
Site Admin
Posts: 6695
Joined: Thu Sep 28, 2006 12:23 am
Contact:

Post by Auria »

http://wiki.wxwidgets.org/Internationalization features the following command :

Code: Select all

CPP_FILE_LIST=`find ./src -name '*.cpp' -print`
xgettext -d app_name -s --keyword=_ -p ./po -o app_name.pot $CPP_FILE_LIST
Maybe you need --keyword=_
"Keyboard not detected. Press F1 to continue"
-- Windows
Nico
Knows some wx things
Knows some wx things
Posts: 31
Joined: Sun Nov 07, 2010 5:19 pm

Post by Nico »

Thanks guys, you were write it was the --keyword=_ part that was missing. It is hard to believe that is what stopped me for three days now.

Thanks a bunch, Nico
Nico
Knows some wx things
Knows some wx things
Posts: 31
Joined: Sun Nov 07, 2010 5:19 pm

Post by Nico »

francis33y wrote:In the program, you need then to init the lang:

Code: Select all

void MyApp::SelectLanguage(int lang)
{
	delete m_locale;
	m_locale = new wxLocale(lang);
//	m_locale->AddCatalogLookupPathPrefix(wxGetCwd());
//	m_locale->AddCatalog(wxT("dryer"));
}

long MyApp::Set_Lang(int lng, bool ask)
{
	if (ask)
	{
		const wxString langs[] =
		{
			_T("(System default)"),
			_T("French"),
			_T("English"),
			_T("Portuguese (Brazilian)"),
		};

	  SetExitOnFrameDelete(FALSE);
	  lng = wxGetSingleChoiceIndex(_T("Please choose language:"),_T("Language"), WXSIZEOF(langs), langs);
	  SetExitOnFrameDelete(TRUE);
	}
	APP_LANG=lng;
  switch (lng)
    {
    case 0 : 
		wxGetApp().SelectLanguage(wxLANGUAGE_DEFAULT);
		break;

    case 1 : 
		wxGetApp().SelectLanguage(wxLANGUAGE_FRENCH);
		break;

    case 3 : 
		wxGetApp().SelectLanguage(wxLANGUAGE_PORTUGUESE_BRAZILIAN);
//		wxGetApp().SelectLanguage(wxLANGUAGE_PORTUGUESE);
		break;

    case -1:
    case 2 : 
		wxGetApp().SelectLanguage(wxLANGUAGE_ENGLISH_US);
		break;
    } 
	return lng;
}
and you need to init all of that

Code: Select all

bool MyApp::OnInit()
{
	m_locale = NULL ;
	SetVendorName(_T("Toto"));
	SetAppName(_T("dryer"));
	wxConfigBase *pConfig = wxConfigBase::Get();
	pConfig->SetRecordDefaults();
	
	if (!pConfig->Read(_T("Lang"),&APP_LANG,-1))
		APP_LANG=Set_Lang(APP_LANG,true);
	else
		APP_LANG=Set_Lang(APP_LANG,false);
	pConfig->Read(_T("Comma"),&APP_WITH_COMMA,false);
    wxString argv0 = wxTheApp->argv[0];
    
    if (wxIsAbsolutePath(argv0))
      m_apppath = argv0;
    else
      {
	wxPathList pathlist;
	pathlist.AddEnvList(wxT("PATH"));
	m_apppath = pathlist.FindAbsoluteValidPath(argv0);
      }
    
    wxFileName filename(m_apppath);
    filename.Normalize();
    m_apppath = filename.GetFullPath();
	m_apppath=wxPathOnly(m_apppath);
#if defined(__WXMAC__)
	m_apppath=GetBundleSharedSupportDir();
#endif
    m_locale->AddCatalogLookupPathPrefix(wxGetCwd());
    m_locale->AddCatalogLookupPathPrefix(m_apppath);
    m_locale->AddCatalogLookupPathPrefix(m_apppath+ wxFILE_SEP_PATH + _T("locale"));
   if (!m_locale->AddCatalog(_T("dryer")))
	{
		wxMessageBox( _T("Couldn't find a catalog for current language"),_T("Info"), wxOK | wxICON_INFORMATION );
		//return false;
	}

....
Ok, I didn't see this one comming. I took your initialization code, changed the scope to mainApp and the domain to test and included the headers that I could find.

I still have a couple of things I don't get yet:
- why do I need this part of the code? I tried and it won't work without so you are probably right, but I don't understand the why.
- where and as what should m_locale , APP_LANG and APP_WITH_COMMA be declared?
- does this enable/is there a way to changing the language in runtime? Just out of curiosity.
francis33y wrote:Why don't you use poedit ?
I do, that is the part I don't have trouble with (yet).

Kind regards, Nico
francis33y
Experienced Solver
Experienced Solver
Posts: 70
Joined: Wed Feb 13, 2008 9:21 am
Location: Montpellier, France

Post by francis33y »

Nico,

As said Auria, you should look at the "internat" sample which is less complex than my app.

"This" part is needed to load the lang catalog, providing all necessary paths to access it.

m_locale , APP_LANG and APP_WITH_COMMA are globals, declared after all "includes" and before objects.

the APP_WITH_COMMA var is specific to my program to allow user to force the use of '.' when his locale says it should be ',' (like us in France).

I have never tried to swith lang without asking user to restart app. You'll tell me if it is possible !

F
Nico
Knows some wx things
Knows some wx things
Posts: 31
Joined: Sun Nov 07, 2010 5:19 pm

Post by Nico »

I am starting to feel a bit stupid here, but I still can't get it to work and I believe this is because I just don't understand how it is supposed to go down.

Some questions I have at this moment:
- if I set a language for translation in my mainApp, does this automatically apply for all other classes in the project?
- should I add some code in the other classes to get them translated?
- why is language declared as a global long in the examples, but referred to as an integer in the documentation? Moreover, what should it be?
- what would be the minimum initialization if I want to translate to the windows XP language? (So the user does not get a choice, but the program confirms to the operating system settings)
- is there a simple, working example that shows how to translate strings using wxWidgets and gcc-compiler?
- is there a "translating applications for noobs" explanation somewhere?

Below is the code I currently have, it allows me to extract the strings and create the .mo files. I stored those .mo files in the project folder in a subfolder called "nl" for Dutch and "en" for English.
But when I run the program it detects the correct language, but the translations are not used/found.

Kind regards, Nico



using winXP, code::blocks, wxwdigets 2.8.11 and gcc-compiler

Code: Select all

#include "mainApp.h"

wxLocale* locale;
long language;

IMPLEMENT_APP(test);

void test::initLanguageSupport()
{
    language =  wxLANGUAGE_DEFAULT;

    // the user doesn't get a choice yet
    // if( userWantsAnotherLanguageThanDefault() ) language = getUsersFavoriteLanguage();

    // load language if possible, fall back to english otherwise
    if(wxLocale::IsAvailable(language))
    {
        locale = new wxLocale( language, wxLOCALE_CONV_ENCODING );
        // locale = new wxLocale(language, wxLOCALE_LOAD_DEFAULT);

        // test to see if we get this far succesfully
        wxMessageBox( locale->GetLanguageName(language) ,_T("Info"), wxOK | wxICON_INFORMATION );

        // add locale search paths later, using something like:
        // locale->AddCatalogLookupPathPrefix(_("./vertaal"));

        locale->AddCatalog(_T("test"));

        if(! locale->IsOk() )
        {
            wxMessageBox( _T("selected language is wrong"),_T("Info"), wxOK | wxICON_INFORMATION );
            delete locale;
            locale = new wxLocale( wxLANGUAGE_ENGLISH );
            language = wxLANGUAGE_ENGLISH;
        }
    }
    else
    {
        wxMessageBox( _T("The selected language is not supported by your system.\nTry installing support for this language."),_T("Info"), wxOK | wxICON_INFORMATION );
        locale = new wxLocale( wxLANGUAGE_ENGLISH );
        language = wxLANGUAGE_ENGLISH;
    }

}

bool test::OnInit()
{
    initLanguageSupport();

    //setlocale( LC_ALL, "" );
    //bindtextdomain( "test", "./vertaal/" );
    //textdomain( "test" );

    bool wxsOK = true;
    wxInitAllImageHandlers();
    if ( wxsOK )
    {
    	window Dlg(0);
    	SetTopWindow(&Dlg);
    	Dlg.ShowModal();
    	wxsOK = false;
    }
    return wxsOK;
}

Code: Select all

#ifndef MAINAPP_H
#define MAINAPP_H

#include <wx/intl.h>
#include <wx/stdpaths.h>
#include <wx/config.h>
#include <wx/filename.h>
#include <wx/filefn.h>
#include <wx/msgdlg.h>
//#include <locale.h>
//#include <stdio.h>
//#include <stdlib.h>
//#include "C:\Program Files\GnuWin32\include\libintl
#include "window.h"
#include <wx/image.h>
#include <wx/app.h>


class test : public wxApp
{
    public:
        virtual bool OnInit();
        void initLanguageSupport();
    protected:
    private:

};

#endif // MAINAPP_H

Code: Select all

#include "window.h"

//(*IdInit(window)
const long window::ID_STATICTEXT1 = wxNewId();
//*)

BEGIN_EVENT_TABLE(window,wxDialog)
END_EVENT_TABLE()


window::window(wxWindow* parent,wxWindowID id,const wxPoint& pos,const wxSize& size)
{
    //setlocale( LC_ALL, "" );
    //bindtextdomain( "test", "./vertaal/" );
    //textdomain( "test" );
	//(*Initialize(window)
	Create(parent, id, _("Test window"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE, _T("id"));
	SetClientSize(wxDefaultSize);
	Move(wxDefaultPosition);
	StaticText1 = new wxStaticText(this, ID_STATICTEXT1, _("Dit is een test om te zien of we dit in runtime kunnen vertalen."), wxPoint(56,152), wxDefaultSize, 0, _T("ID_STATICTEXT1"));
	//*)
}

window::~window()
{
	//(*Destroy(window)
	//*)
}

Code: Select all

#ifndef WINDOW_H
#define WINDOW_H

#include <wx/stattext.h>
#include <wx/dialog.h>

#include "C:\Program Files\GnuWin32\include\libintl.h"
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <wx/intl.h>


class window: public wxDialog
{
	public:

		window(wxWindow* parent,wxWindowID id=wxID_ANY,const wxPoint& pos=wxDefaultPosition,const wxSize& size=wxDefaultSize);
		virtual ~window();

		//(*Declarations(window)
		wxStaticText* StaticText1;
		//*)

	protected:

		//(*Identifiers(window)
		static const long ID_STATICTEXT1;
		//*)

	private:

		//(*Handlers(window)
		//*)

		DECLARE_EVENT_TABLE()
};

#endif
Attachments
pleaseHelpMeTranslate.PNG
pleaseHelpMeTranslate.PNG (36.94 KiB) Viewed 2602 times
Auria
Site Admin
Site Admin
Posts: 6695
Joined: Thu Sep 28, 2006 12:23 am
Contact:

Post by Auria »

You will probably need a line like

Code: Select all

locale->AddCatalogLookupPathPrefix( ... ); 
to tell wx where to search for mo files.

And wxLocale works globally, you init it once and then all _(...) calls start translating your strings
"Keyboard not detected. Press F1 to continue"
-- Windows
Nico
Knows some wx things
Knows some wx things
Posts: 31
Joined: Sun Nov 07, 2010 5:19 pm

Post by Nico »

Hoeray, somehow I got it to work. Thank you very much for all of your help.

Kind regards, Nico
Post Reply