diff --git a/clientgui/BOINCGUIApp.cpp b/clientgui/BOINCGUIApp.cpp index d7fe6832371..bc889fabecb 100644 --- a/clientgui/BOINCGUIApp.cpp +++ b/clientgui/BOINCGUIApp.cpp @@ -102,6 +102,7 @@ bool CBOINCGUIApp::OnInit() { m_iRPCPortArg = GUI_RPC_PORT; m_strBOINCArguments = wxEmptyString; m_strISOLanguageCode = wxEmptyString; + m_bUseDefaultLocale = true; m_bGUIVisible = true; m_bDebugSkins = false; m_bMultipleInstancesOK = false; @@ -120,7 +121,7 @@ bool CBOINCGUIApp::OnInit() { m_bNeedRunDaemon = true; // Initialize local variables - int iDesiredLanguageCode = 0; + int iDesiredLanguageCode = wxLANGUAGE_DEFAULT; bool bOpenEventLog = false; wxString strDesiredSkinName = wxEmptyString; #ifdef SANDBOX @@ -198,6 +199,10 @@ bool CBOINCGUIApp::OnInit() { #endif m_pConfig->Read(wxT("DisableAutoStart"), &m_iBOINCMGRDisableAutoStart, 0L); m_pConfig->Read(wxT("LanguageISO"), &m_strISOLanguageCode, wxT("")); + bool bUseDefaultLocaleDefault = + // Migration: assume a selected language code that matches the system default means "auto select" + m_strISOLanguageCode == wxLocale::GetLanguageInfo(wxLANGUAGE_DEFAULT)->CanonicalName; + m_pConfig->Read(wxT("UseDefaultLocale"), &m_bUseDefaultLocale, bUseDefaultLocaleDefault); m_pConfig->Read(wxT("GUISelection"), &m_iGUISelected, BOINC_SIMPLEGUI); m_pConfig->Read(wxT("EventLogOpen"), &bOpenEventLog); m_pConfig->Read(wxT("RunDaemon"), &m_bRunDaemon, 1L); @@ -267,12 +272,15 @@ bool CBOINCGUIApp::OnInit() { wxASSERT(m_pLocale); // - if (m_strISOLanguageCode.IsEmpty()) { - iDesiredLanguageCode = wxLANGUAGE_DEFAULT; - m_pLocale->Init(iDesiredLanguageCode); + if (!m_bUseDefaultLocale && !m_strISOLanguageCode.IsEmpty()) { + const wxLanguageInfo* pLI = wxLocale::FindLanguageInfo(m_strISOLanguageCode); + if (pLI) { + iDesiredLanguageCode = pLI->Language; + } + } + m_pLocale->Init(iDesiredLanguageCode); + if (iDesiredLanguageCode == wxLANGUAGE_DEFAULT) { m_strISOLanguageCode = m_pLocale->GetCanonicalName(); - } else { - m_pLocale->Init(wxLocale::FindLanguageInfo(m_strISOLanguageCode)->Language); } // Look for the localization files by absolute and relative locations. @@ -598,6 +606,7 @@ void CBOINCGUIApp::SaveState() { m_pConfig->Write(wxT("Skin"), m_pSkinManager->GetSelectedSkin()); } m_pConfig->Write(wxT("LanguageISO"), m_strISOLanguageCode); + m_pConfig->Write(wxT("UseDefaultLocale"), m_bUseDefaultLocale); m_pConfig->Write(wxT("AutomaticallyShutdownClient"), m_iShutdownCoreClient); m_pConfig->Write(wxT("DisplayShutdownClientDialog"), m_iDisplayExitDialog); m_pConfig->Write(wxT("DisplayShutdownConnectedClientDialog"), m_iDisplayShutdownConnectedClientDialog); @@ -893,22 +902,110 @@ void CBOINCGUIApp::DetectDataDirectory() { void CBOINCGUIApp::InitSupportedLanguages() { - wxInt32 iIndex = 0; - const wxLanguageInfo* liLanguage = NULL; - - // Prepare the array - m_astrLanguages.Insert(wxEmptyString, 0, wxLANGUAGE_USER_DEFINED+1); - - // These are just special tags so deal with them in a special way - m_astrLanguages[wxLANGUAGE_DEFAULT] = _("(Automatic Detection)"); - m_astrLanguages[wxLANGUAGE_UNKNOWN] = _("(Unknown)"); - m_astrLanguages[wxLANGUAGE_USER_DEFINED] = _("(User Defined)"); + m_astrLanguages.clear(); + + // Find available translations + std::vector availableTranslations; + // English is a special case: + // - it's guaranteed to be available because it's compiled in + // - it must be added to the list even though we don't expect to find a translation for it + const wxLanguageInfo* pLIen = wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH); + if (pLIen) { + availableTranslations.push_back(pLIen); + } + // Now fill in the rest from the available message catalogs + const wxTranslations* pTranslations = wxTranslations::Get(); + if (pTranslations) { + wxArrayString langCodes = pTranslations->GetAvailableTranslations(wxT("BOINC-Manager")); + for (const wxString& langCode : langCodes) { + if (langCode == wxT("en")) continue; + const wxLanguageInfo* pLI = wxLocale::FindLanguageInfo(langCode); + if (pLI) { + availableTranslations.push_back(pLI); + } + } + } - for (iIndex = 0; iIndex <= wxLANGUAGE_USER_DEFINED; iIndex++) { - liLanguage = wxLocale::GetLanguageInfo(iIndex); - if (liLanguage) { - m_astrLanguages[iIndex] = liLanguage->Description; + // Synthesize labels to be used in the options dialog + // + // As we are building strings that potentially contain both left-to-right and + // right-to-left text, we must insert direction markers to ensure the layout is + // correct. Otherwise strange things happen - particularly when the strings contain + // parentheses, which can end up in the wrong place and pointing the wrong way. + // The usage here has been determined largely by trial and error, and may not be + // strictly correct... + const wxString LRM = L'\x200E'/*LEFT-TO-RIGHT MARK*/; + const wxString RLM = L'\x200F'/*RIGHT-TO-LEFT MARK*/; + const wxLanguageInfo* pLIui = wxLocale::FindLanguageInfo(GetISOLanguageCode()); + wxLayoutDirection uiLayoutDirection = pLIui ? pLIui->LayoutDirection : wxLayout_Default; + GUI_SUPPORTED_LANG newItem; + + // CDlgOptions depends on "Auto" being the first item in the list + newItem.Language = wxLANGUAGE_DEFAULT; + wxString strAutoEnglish = wxT("(Automatic Detection)"); + wxString strAutoTranslated = wxGetTranslation(strAutoEnglish); + newItem.Label = strAutoTranslated; + if (strAutoTranslated != strAutoEnglish) { + if (uiLayoutDirection == wxLayout_RightToLeft) { + newItem.Label += RLM; + } else if (uiLayoutDirection == wxLayout_LeftToRight) { + newItem.Label += LRM; + } + newItem.Label += wxT(" "); + if (uiLayoutDirection == wxLayout_RightToLeft) { + newItem.Label += RLM; } + newItem.Label += LRM + strAutoEnglish + LRM; + } + m_astrLanguages.push_back(newItem); + + // Add known locales to the list + for (int langID = wxLANGUAGE_UNKNOWN+1; langID < wxLANGUAGE_USER_DEFINED; ++langID) { + const wxLanguageInfo* pLI = wxLocale::GetLanguageInfo(langID); + wxString lang_region = pLI->CanonicalName.BeforeFirst('@'); + wxString lang = lang_region.BeforeFirst('_'); + wxString script = pLI->CanonicalName.AfterFirst('@'); + wxString lang_script = lang; + if (!script.empty()) { + lang_script += wxT("@") + script; + } + std::vector::const_iterator foundit = availableTranslations.begin(); + while (foundit != availableTranslations.end()) { + const wxLanguageInfo* pLIavail = *foundit; + if (pLIavail->CanonicalName == lang_script || + pLIavail->CanonicalName == pLI->CanonicalName) { + break; + } + ++foundit; + } + // If we don't have a translation, don't add to the list - + // unless the locale has been explicitly selected by the user + // (setting migrated from an earlier version, or manually configured) + if (foundit == availableTranslations.end() && pLI != pLIui) continue; + newItem.Language = langID; +#if wxCHECK_VERSION(3,1,6) + if (pLI->DescriptionNative != pLI->Description && + !pLI->DescriptionNative.empty()) { + // The "NativeName (EnglishName)" format of the label matches that used + // for Web sites [language_select() in html/inc/language_names.inc] + newItem.Label = pLI->DescriptionNative; + if (pLI->LayoutDirection == wxLayout_RightToLeft) { + newItem.Label += RLM; + } else if (pLI->LayoutDirection == wxLayout_LeftToRight) { + newItem.Label += LRM; + } + newItem.Label += wxT(" "); + if (uiLayoutDirection == wxLayout_RightToLeft) { + newItem.Label += RLM; + } + newItem.Label += LRM + wxT("(") + pLI->Description + wxT(")") + LRM; + } else { + newItem.Label = pLI->Description + LRM; + } +#else + newItem.Label = pLI->Description + LRM; +#endif + m_astrLanguages.push_back(newItem); } } diff --git a/clientgui/BOINCGUIApp.h b/clientgui/BOINCGUIApp.h index 5cc91220dfa..4b4b6d69526 100644 --- a/clientgui/BOINCGUIApp.h +++ b/clientgui/BOINCGUIApp.h @@ -1,6 +1,6 @@ // This file is part of BOINC. // http://boinc.berkeley.edu -// Copyright (C) 2016 University of California +// Copyright (C) 2023 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -45,6 +45,11 @@ class CSkinManager; class CDlgEventLog; class CRPCFinishedEvent; +struct GUI_SUPPORTED_LANG { + int Language; // wxLanguage ID, used to set the locale + wxString Label; // Text to display in the options dialog +}; + #ifdef __WXMAC__ OSErr QuitAppleEventHandler(const AppleEvent *appleEvt, AppleEvent* reply, UInt32 refcon); #endif @@ -117,10 +122,9 @@ class CBOINCGUIApp : public wxApp { bool m_bRunDaemon; bool m_bNeedRunDaemon; - // The last value defined in the wxLanguage enum is wxLANGUAGE_USER_DEFINED. - // defined in: wx/intl.h - wxArrayString m_astrLanguages; + std::vector m_astrLanguages; wxString m_strISOLanguageCode; + bool m_bUseDefaultLocale; int m_bSafeMessageBoxDisplayed; @@ -187,10 +191,12 @@ class CBOINCGUIApp : public wxApp { bool GetNeedRunDaemon() { return m_bNeedRunDaemon; } - wxArrayString& GetSupportedLanguages() { return m_astrLanguages; } + const std::vector& GetSupportedLanguages() const { return m_astrLanguages; } wxString GetISOLanguageCode() { return m_strISOLanguageCode; } void SetISOLanguageCode(wxString strISOLanguageCode) { m_strISOLanguageCode = strISOLanguageCode; } + bool UseDefaultLocale() const { return m_bUseDefaultLocale; } + void SetUseDefaultLocale(bool b) { m_bUseDefaultLocale = b; } void SetEventLogWasActive(bool wasActive) { m_bEventLogWasActive = wasActive; } void DisplayEventLog(bool bShowWindow = true); diff --git a/clientgui/DlgOptions.cpp b/clientgui/DlgOptions.cpp index 3e9c48e6e6e..ae9c8bdcc07 100644 --- a/clientgui/DlgOptions.cpp +++ b/clientgui/DlgOptions.cpp @@ -1,6 +1,6 @@ // This file is part of BOINC. // http://boinc.berkeley.edu -// Copyright (C) 2008 University of California +// Copyright (C) 2023 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License @@ -183,9 +183,13 @@ void CDlgOptions::CreateControls() { itemStaticText7->Create( itemPanel4, wxID_STATIC, _("Language:"), wxDefaultPosition, wxDefaultSize, 0 ); itemFlexGridSizer6->Add(itemStaticText7, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxALL, 5); - wxString* m_LanguageSelectionCtrlStrings = NULL; + const std::vector& langs = wxGetApp().GetSupportedLanguages(); + wxArrayString langLabels; + for (const GUI_SUPPORTED_LANG& lang : langs) { + langLabels.push_back(lang.Label); + } m_LanguageSelectionCtrl = new wxComboBox; - m_LanguageSelectionCtrl->Create( itemPanel4, ID_LANGUAGESELECTION, wxT(""), wxDefaultPosition, wxDefaultSize, 0, m_LanguageSelectionCtrlStrings, wxCB_READONLY ); + m_LanguageSelectionCtrl->Create( itemPanel4, ID_LANGUAGESELECTION, wxT(""), wxDefaultPosition, wxDefaultSize, langLabels, wxCB_READONLY ); if (ShowToolTips()) m_LanguageSelectionCtrl->SetToolTip(_("What language should BOINC use?")); itemFlexGridSizer6->Add(m_LanguageSelectionCtrl, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxALL, 5); @@ -620,8 +624,24 @@ bool CDlgOptions::ReadSettings() { // General Tab - m_LanguageSelectionCtrl->Append(wxGetApp().GetSupportedLanguages()); - m_LanguageSelectionCtrl->SetSelection(wxLocale::FindLanguageInfo(wxGetApp().GetISOLanguageCode())->Language); + if (wxGetApp().UseDefaultLocale()) { + // CBOINCGUIApp::InitSupportedLanguages() ensures "Auto" is the first item in the list + m_LanguageSelectionCtrl->SetSelection(0); + } else { + const wxLanguageInfo* pLI = wxLocale::FindLanguageInfo(wxGetApp().GetISOLanguageCode()); + if (pLI) { + const std::vector& langs = wxGetApp().GetSupportedLanguages(); + for (std::vector::const_iterator foundit = langs.begin(); + foundit != langs.end(); ++foundit) { + const GUI_SUPPORTED_LANG& item = *foundit; + if (item.Language == pLI->Language) { + int selLangIdx = std::distance(langs.begin(), foundit); + m_LanguageSelectionCtrl->SetSelection(selLangIdx); + break; + } + } + } + } m_ReminderFrequencyCtrl->Append(_("always")); m_ReminderFrequencyCtrl->Append(_("1 hour")); @@ -723,7 +743,20 @@ bool CDlgOptions::SaveSettings() { // General Tab - if (wxLocale::FindLanguageInfo(wxGetApp().GetISOLanguageCode())->Language != m_LanguageSelectionCtrl->GetSelection()) { + wxString oldLangCode = wxGetApp().GetISOLanguageCode(); + wxString newLangCode = oldLangCode; + int selLangIdx = m_LanguageSelectionCtrl->GetSelection(); + if (selLangIdx == 0) { + // CBOINCGUIApp::InitSupportedLanguages() ensures "Auto" is the first item in the list + newLangCode = wxLocale::GetLanguageInfo(wxLANGUAGE_DEFAULT)->CanonicalName; + } else if (selLangIdx > 0) { + const std::vector& langs = wxGetApp().GetSupportedLanguages(); + if (selLangIdx < langs.size()) { + const GUI_SUPPORTED_LANG& selLang = langs[selLangIdx]; + newLangCode = wxLocale::GetLanguageInfo(selLang.Language)->CanonicalName; + } + } + if (newLangCode != oldLangCode) { wxString strDialogTitle; wxString strDialogMessage; @@ -749,7 +782,8 @@ bool CDlgOptions::SaveSettings() { ); } - wxGetApp().SetISOLanguageCode(wxLocale::GetLanguageInfo(m_LanguageSelectionCtrl->GetSelection())->CanonicalName); + wxGetApp().SetISOLanguageCode(newLangCode); + wxGetApp().SetUseDefaultLocale(selLangIdx == 0); switch(m_ReminderFrequencyCtrl->GetSelection()) { case 0: diff --git a/html/inc/language_names.inc b/html/inc/language_names.inc index d59d4bec705..e367af0acac 100644 --- a/html/inc/language_names.inc +++ b/html/inc/language_names.inc @@ -18,7 +18,7 @@ $language_names = array( array("ar", "العربية", "Arabic"), - array("az", "Азәрбајҹан дили", "Azerbaijani"), + array("az", "Azərbaycan", "Azerbaijani"), array("be", "Беларускі", "Belarusian"), array("bg", "Български", "Bulgarian"), array("ca", "Català", "Catalan"),