Merge branch 'ui-locale'

Add wxUILocale class providing functionality which can be implemented
portably for all major platforms, including macOS, and doesn't force
the change of the global C locale, unlike wxLocale.

See https://github.com/wxWidgets/wxWidgets/pull/2464
This commit is contained in:
Vadim Zeitlin
2021-08-26 15:49:57 +02:00
41 changed files with 1978 additions and 655 deletions

View File

@@ -28,11 +28,17 @@
#include "wx/wx.h"
#endif
#include "wx/calctrl.h"
#include "wx/intl.h"
#include "wx/file.h"
#include "wx/grid.h"
#include "wx/log.h"
#include "wx/cmdline.h"
#include "wx/numformatter.h"
#include "wx/platinfo.h"
#include "wx/spinctrl.h"
#include "wx/translation.h"
#include "wx/uilocale.h"
#ifndef wxHAS_IMAGES_IN_RESOURCES
#include "../sample.xpm"
@@ -53,22 +59,29 @@
class MyApp: public wxApp
{
public:
MyApp() { m_lang = wxLANGUAGE_UNKNOWN; }
MyApp() { m_setLocale = Locale_Ask; }
virtual void OnInitCmdLine(wxCmdLineParser& parser) wxOVERRIDE;
virtual bool OnCmdLineParsed(wxCmdLineParser& parser) wxOVERRIDE;
virtual bool OnInit() wxOVERRIDE;
protected:
wxLanguage m_lang; // language specified by user
wxLocale m_locale; // locale we'll be using
// Specifies whether we should use the current locale or not. By default we
// ask the user about it, but it's possible to override this using the
// command line options.
enum
{
Locale_Ask,
Locale_Set,
Locale_Skip
} m_setLocale;
};
// Define a new frame type
class MyFrame: public wxFrame
{
public:
MyFrame(wxLocale& m_locale);
MyFrame();
public:
void OnTestLocaleAvail(wxCommandEvent& event);
@@ -80,14 +93,13 @@ public:
void OnPlay(wxCommandEvent& event);
void OnOpen(wxCommandEvent& event);
void OnSave(wxCommandEvent& event);
void OnTest1(wxCommandEvent& event);
void OnTest2(wxCommandEvent& event);
void OnTest3(wxCommandEvent& event);
void OnTestMsgBox(wxCommandEvent& event);
wxDECLARE_EVENT_TABLE();
wxLocale& m_locale;
};
// ----------------------------------------------------------------------------
@@ -114,59 +126,6 @@ enum
INTERNAT_MACRO_9
};
// language data
static const wxLanguage langIds[] =
{
wxLANGUAGE_DEFAULT,
wxLANGUAGE_FRENCH,
wxLANGUAGE_ITALIAN,
wxLANGUAGE_GERMAN,
wxLANGUAGE_RUSSIAN,
wxLANGUAGE_BULGARIAN,
wxLANGUAGE_CZECH,
wxLANGUAGE_POLISH,
wxLANGUAGE_SWEDISH,
#if wxUSE_UNICODE || defined(__WXMOTIF__)
wxLANGUAGE_JAPANESE,
#endif
#if wxUSE_UNICODE
wxLANGUAGE_GEORGIAN,
wxLANGUAGE_ENGLISH,
wxLANGUAGE_ENGLISH_US,
wxLANGUAGE_ARABIC,
wxLANGUAGE_ARABIC_EGYPT
#endif
};
// note that it makes no sense to translate these strings, they are
// shown before we set the locale anyhow
const wxString langNames[] =
{
"System default",
"French",
"Italian",
"German",
"Russian",
"Bulgarian",
"Czech",
"Polish",
"Swedish",
#if wxUSE_UNICODE || defined(__WXMOTIF__)
"Japanese",
#endif
#if wxUSE_UNICODE
"Georgian",
"English",
"English (U.S.)",
"Arabic",
"Arabic (Egypt)"
#endif
};
// the arrays must be in sync
wxCOMPILE_TIME_ASSERT( WXSIZEOF(langNames) == WXSIZEOF(langIds),
LangArraysMismatch );
// ----------------------------------------------------------------------------
// wxWidgets macros
// ----------------------------------------------------------------------------
@@ -181,6 +140,7 @@ wxBEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_MENU(INTERNAT_PLAY, MyFrame::OnPlay)
EVT_MENU(wxID_OPEN, MyFrame::OnOpen)
EVT_MENU(wxID_SAVE, MyFrame::OnSave)
EVT_MENU(INTERNAT_TEST_1, MyFrame::OnTest1)
EVT_MENU(INTERNAT_TEST_2, MyFrame::OnTest2)
EVT_MENU(INTERNAT_TEST_3, MyFrame::OnTest3)
@@ -198,11 +158,16 @@ wxIMPLEMENT_APP(MyApp);
// ----------------------------------------------------------------------------
// command line arguments handling
static const char* OPTION_NO_LOCALE = "no-locale";
static const char* OPTION_SET_LOCALE = "set-locale";
void MyApp::OnInitCmdLine(wxCmdLineParser& parser)
{
parser.AddParam(_("locale"),
wxCMD_LINE_VAL_STRING,
wxCMD_LINE_PARAM_OPTIONAL);
parser.AddSwitch("n", OPTION_NO_LOCALE,
_("skip setting locale on startup"));
parser.AddSwitch("y", OPTION_SET_LOCALE,
_("do set locale on startup without asking"));
wxApp::OnInitCmdLine(parser);
}
@@ -212,17 +177,20 @@ bool MyApp::OnCmdLineParsed(wxCmdLineParser& parser)
if ( !wxApp::OnCmdLineParsed(parser) )
return false;
if ( parser.GetParamCount() )
if ( parser.Found(OPTION_NO_LOCALE) )
{
const wxString loc = parser.GetParam();
const wxLanguageInfo * const lang = wxLocale::FindLanguageInfo(loc);
if ( !lang )
m_setLocale = Locale_Skip;
}
if ( parser.Found(OPTION_SET_LOCALE) )
{
if ( m_setLocale == Locale_Skip )
{
wxLogError(_("Locale \"%s\" is unknown."), loc);
return false;
wxLogWarning("--%s option overrides --%s",
OPTION_SET_LOCALE, OPTION_NO_LOCALE);
}
m_lang = static_cast<wxLanguage>(lang->Language);
m_setLocale = Locale_Set;
}
return true;
@@ -234,57 +202,75 @@ bool MyApp::OnInit()
if ( !wxApp::OnInit() )
return false;
if ( m_lang == wxLANGUAGE_UNKNOWN )
// For demonstration purposes only, ask the user if they want to run the
// program using the current system language. In real programs, we would do
// it unconditionally for localized programs -- or never do it at all for
// the other ones.
const wxLanguageInfo* const
langInfo = wxLocale::GetLanguageInfo(wxLANGUAGE_DEFAULT);
const wxString
langDesc = langInfo ? langInfo->Description
: "the default system locale";
if ( m_setLocale == Locale_Ask )
{
int lng = wxGetSingleChoiceIndex
(
_("Please choose language:"),
_("Language"),
WXSIZEOF(langNames),
langNames
);
m_lang = lng == -1 ? wxLANGUAGE_DEFAULT : langIds[lng];
m_setLocale = wxMessageBox
(
wxString::Format
(
"Would you like to use the program in %s?",
langDesc
),
"wxWidgets i18n (internat) sample",
wxYES_NO
) == wxYES ? Locale_Set : Locale_Skip;
}
// don't use wxLOCALE_LOAD_DEFAULT flag so that Init() doesn't return
// false just because it failed to load wxstd catalog
if ( !m_locale.Init(m_lang, wxLOCALE_DONT_LOAD_DEFAULT) )
if ( m_setLocale == Locale_Set )
{
wxLogWarning(_("This language is not supported by the system."));
if ( !wxUILocale::UseDefault() )
{
wxLogWarning("Failed to initialize the default system locale.");
}
// continue nevertheless
}
// normally this wouldn't be necessary as the catalog files would be found
// in the default locations, but when the program is not installed the
// catalogs are in the build directory where we wouldn't find them by
// default
wxLocale::AddCatalogLookupPathPrefix(".");
// Independently of whether we succeeded to set the locale or not, try
// to load the translations (for the default system language) here.
// Initialize the catalogs we'll be using
const wxLanguageInfo* pInfo = wxLocale::GetLanguageInfo(m_lang);
if (!m_locale.AddCatalog("internat"))
{
wxLogError(_("Couldn't find/load the 'internat' catalog for locale '%s'."),
pInfo ? pInfo->GetLocaleName() : _("unknown"));
}
// normally this wouldn't be necessary as the catalog files would be found
// in the default locations, but when the program is not installed the
// catalogs are in the build directory where we wouldn't find them by
// default
wxFileTranslationsLoader::AddCatalogLookupPathPrefix(".");
// Now try to add wxstd.mo so that loading "NOTEXIST.ING" file will produce
// a localized error message:
m_locale.AddCatalog("wxstd");
// NOTE: it's not an error if we couldn't find it!
// Create the object for message translation and set it up for global use.
wxTranslations* const trans = new wxTranslations();
wxTranslations::Set(trans);
// this catalog is installed in standard location on Linux systems and
// shows that you may make use of the standard message catalogs as well
//
// if it's not installed on your system, it is just silently ignored
// Initialize the catalogs we'll be using.
if ( !trans->AddCatalog("internat") )
{
wxLogError(_("Couldn't find/load 'internat' catalog for %s."),
langDesc);
}
// Now try to add wxstd.mo so that loading "NOTEXIST.ING" file will produce
// a localized error message:
trans->AddCatalog("wxstd");
// NOTE: it's not an error if we couldn't find it!
// this catalog is installed in standard location on Linux systems and
// shows that you may make use of the standard message catalogs as well
//
// if it's not installed on your system, it is just silently ignored
#ifdef USE_COREUTILS_MO
wxLocale::AddCatalogLookupPathPrefix("/usr/share/locale");
g_loadedCoreutilsMO = m_locale.AddCatalog("coreutils");
wxFileTranslationsLoader::AddCatalogLookupPathPrefix("/usr/share/locale");
g_loadedCoreutilsMO = trans->AddCatalog("coreutils");
#endif // USE_COREUTILS_MO
}
// Create the main frame window
MyFrame *frame = new MyFrame(m_locale);
MyFrame *frame = new MyFrame();
// Show the frame
frame->Show(true);
@@ -297,11 +283,10 @@ bool MyApp::OnInit()
// ----------------------------------------------------------------------------
// main frame constructor
MyFrame::MyFrame(wxLocale& locale)
MyFrame::MyFrame()
: wxFrame(NULL,
wxID_ANY,
_("International wxWidgets App")),
m_locale(locale)
_("International wxWidgets App"))
{
SetIcon(wxICON(sample));
@@ -316,6 +301,7 @@ MyFrame::MyFrame(wxLocale& locale)
wxMenu *test_menu = new wxMenu;
test_menu->Append(wxID_OPEN, _("&Open bogus file"), _("Shows a wxWidgets localized error message"));
test_menu->Append(wxID_SAVE, _("&Save dummy file"), _("Shows a localized standard dialog"));
test_menu->Append(INTERNAT_PLAY, _("&Play a game"), _("A little game; hint: 17 is a lucky number for many"));
test_menu->AppendSeparator();
test_menu->Append(INTERNAT_TEST_1, _("&1 _() (gettext)"), _("Tests the _() macro"));
@@ -360,13 +346,79 @@ MyFrame::MyFrame(wxLocale& locale)
// this demonstrates RTL support in wxStatusBar:
CreateStatusBar(1);
// this demonstrates RTL layout mirroring for Arabic locales
wxPanel* const panel = new wxPanel(this);
wxSizer* const topSizer = new wxBoxSizer(wxVERTICAL);
// create controls showing the locale being used
topSizer->Add(new wxStaticText
(
panel,
wxID_ANY,
wxString::Format
(
_("Current UI locale: %s; C locale: %s"),
wxUILocale::GetCurrent().GetName(),
setlocale(LC_ALL, NULL)
)
),
wxSizerFlags().Center().Border());
// create some controls affected by the locale
// this demonstrates RTL layout mirroring for Arabic locales and using
// locale-specific decimal separator in wxSpinCtrlDouble.
wxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(new wxStaticText(this, wxID_ANY, _("First")),
wxSizerFlags().Border());
sizer->Add(new wxStaticText(this, wxID_ANY, _("Second")),
wxSizerFlags().Border());
SetSizer(sizer);
sizer->Add(new wxStaticText(panel, wxID_ANY, _("Numeric input:")),
wxSizerFlags().Center().Border());
wxSpinCtrlDouble* const spin = new wxSpinCtrlDouble(panel, wxID_ANY);
spin->SetDigits(2);
spin->SetValue(12.34);
sizer->Add(spin, wxSizerFlags().Center().Border());
topSizer->Add(sizer, wxSizerFlags().Center());
// show that week days and months names are translated too
topSizer->Add(new wxCalendarCtrl(panel, wxID_ANY),
wxSizerFlags().Center().Border());
// another control using locale-specific number and date format
wxGrid* const grid = new wxGrid(panel, wxID_ANY,
wxDefaultPosition, wxDefaultSize,
wxBORDER_SIMPLE);
grid->CreateGrid(2, 2);
grid->HideRowLabels();
grid->SetColLabelValue(0, _("Number"));
grid->SetColFormatFloat(0);
grid->SetCellValue(0, 0, wxNumberFormatter::ToString(3.14159265, -1));
grid->SetColLabelValue(1, _("Date"));
grid->SetColFormatDate(1);
grid->SetCellValue(0, 1, "Today");
topSizer->Add(grid, wxSizerFlags().Center().Border());
// show the difference (in decimal and thousand separator, hence use a
// floating point number > 1000) between wxString::Format() and
// wxNumberFormatter: the former uses the current C locale, while the
// latter uses the UI locale
topSizer->Add(new wxStaticText
(
panel,
wxID_ANY,
wxString::Format
(
_("Number in UI locale: %s; in C locale: %.2f"),
wxNumberFormatter::ToString(1234567.89, 2),
1234567.89
)
),
wxSizerFlags().Center().Border());
panel->SetSizer(topSizer);
topSizer->SetSizeHints(this);
}
void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event) )
@@ -376,20 +428,10 @@ void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event) )
void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
{
wxString localeInfo;
wxString locale = m_locale.GetLocale();
wxString sysname = m_locale.GetSysName();
wxString canname = m_locale.GetCanonicalName();
localeInfo.Printf(_("Language: %s\nSystem locale name: %s\nCanonical locale name: %s\n"),
locale, sysname, canname );
wxMessageDialog dlg(
this,
wxString(_("I18n sample\n(c) 1998, 1999 Vadim Zeitlin and Julian Smart"))
+ "\n\n"
+ localeInfo,
_("About Internat"),
_("I18n sample\n(c) 1998, 1999 Vadim Zeitlin and Julian Smart"),
_("About Internat"),
wxOK | wxICON_INFORMATION
);
dlg.ShowModal();
@@ -510,6 +552,13 @@ void MyFrame::OnOpen(wxCommandEvent& WXUNUSED(event))
wxFile file("NOTEXIST.ING");
}
void MyFrame::OnSave(wxCommandEvent& WXUNUSED(event))
{
// show this file dialog just to check that the locale-specific elements in
// it (such as dates) follow the current locale convnetions
wxSaveFileSelector(_("Dummy file dialog"), ".ext", "dummy", this);
}
void MyFrame::OnTest1(wxCommandEvent& WXUNUSED(event))
{
const wxString& title = _("Testing _() (gettext)");