From 4b7465696d05ed61a968f54db04f258d54a119f1 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Wed, 18 Apr 2018 10:42:58 -0400 Subject: [PATCH 1/8] [welcome] Refactor the code that picks a locale to use - Much like std::find_if, but slightly muddled because there's no iterator that we can sensibly use. - Scan the ComboBox for a locale that matches a predicate. - Log more as the search for a good locale progresses. - Don't mix matching the locale with filling the ComboBox (even though that's slightly more efficient). --- src/modules/welcome/WelcomePage.cpp | 146 +++++++++++++++------------- 1 file changed, 81 insertions(+), 65 deletions(-) diff --git a/src/modules/welcome/WelcomePage.cpp b/src/modules/welcome/WelcomePage.cpp index 5781b3a58..8bee436e6 100644 --- a/src/modules/welcome/WelcomePage.cpp +++ b/src/modules/welcome/WelcomePage.cpp @@ -106,90 +106,106 @@ WelcomePage::WelcomePage( RequirementsChecker* requirementsChecker, QWidget* par } +/** @brief Match the combobox of languages with a predicate + * + * Scans the entries in the @p list (actually a ComboBox) and if one + * matches the given @p predicate, returns true and sets @p matchFound + * to the locale that matched. + * + * If none match, returns false and leaves @p matchFound unchanged. + */ +static +bool matchLocale( QComboBox& list, QLocale& matchFound, std::function predicate) +{ + for (int i = 0; i < list.count(); i++) + { + QLocale thisLocale = list.itemData( i, Qt::UserRole ).toLocale(); + if ( predicate(thisLocale) ) + { + list.setCurrentIndex( i ); + cDebug() << " .. Matched locale " << thisLocale.name(); + matchFound = thisLocale; + return true; + } + } + + return false; +} + void WelcomePage::initLanguages() { ui->languageWidget->setInsertPolicy( QComboBox::InsertAlphabetically ); QLocale defaultLocale = QLocale( QLocale::system().name() ); - { - bool isTranslationAvailable = false; + // Fill the list of translations + { const auto locales = QString( CALAMARES_TRANSLATION_LANGUAGES ).split( ';'); for ( const QString& locale : locales ) { QLocale thisLocale = QLocale( locale ); QString lang = QLocale::languageToString( thisLocale.language() ); + if ( QLocale::countriesForLanguage( thisLocale.language() ).count() > 2 ) lang.append( QString( " (%1)" ) .arg( QLocale::countryToString( thisLocale.country() ) ) ); ui->languageWidget->addItem( lang, thisLocale ); - if ( thisLocale.language() == defaultLocale.language() && - thisLocale.country() == defaultLocale.country() ) - { - isTranslationAvailable = true; - ui->languageWidget->setCurrentIndex( ui->languageWidget->count() - 1 ); - cDebug() << "Initial locale " << thisLocale.name(); - CalamaresUtils::installTranslator( thisLocale.name(), - Calamares::Branding::instance()->translationsPathPrefix(), - qApp ); - } } - - if ( !isTranslationAvailable ) - { - for (int i = 0; i < ui->languageWidget->count(); i++) - { - QLocale thisLocale = ui->languageWidget->itemData( i, Qt::UserRole ).toLocale(); - if ( thisLocale.language() == defaultLocale.language() ) - { - isTranslationAvailable = true; - ui->languageWidget->setCurrentIndex( i ); - cDebug() << "Initial locale " << thisLocale.name(); - CalamaresUtils::installTranslator( thisLocale.name(), - Calamares::Branding::instance()->translationsPathPrefix(), - qApp ); - break; - } - } - } - - if ( !isTranslationAvailable ) - { - for (int i = 0; i < ui->languageWidget->count(); i++) - { - QLocale thisLocale = ui->languageWidget->itemData( i, Qt::UserRole ).toLocale(); - if ( thisLocale == QLocale( QLocale::English, QLocale::UnitedStates ) ) - { - isTranslationAvailable = true; - ui->languageWidget->setCurrentIndex( i ); - cDebug() << "Translation unavailable, so initial locale set to " << thisLocale.name(); - QLocale::setDefault( thisLocale ); - CalamaresUtils::installTranslator( thisLocale.name(), - Calamares::Branding::instance()->translationsPathPrefix(), - qApp ); - break; - } - } - } - - if ( !isTranslationAvailable ) - cWarning() << "No available translation matched" << defaultLocale; - - connect( ui->languageWidget, - static_cast< void ( QComboBox::* )( int ) >( &QComboBox::currentIndexChanged ), - this, [ & ]( int newIndex ) - { - QLocale selectedLocale = ui->languageWidget->itemData( newIndex, Qt::UserRole ).toLocale(); - cDebug() << "Selected locale" << selectedLocale.name(); - - QLocale::setDefault( selectedLocale ); - CalamaresUtils::installTranslator( selectedLocale, - Calamares::Branding::instance()->translationsPathPrefix(), - qApp ); - } ); } + + // Find the best initial translation + QLocale selectedLocale; + + cDebug() << "Matching exact locale" << defaultLocale; + bool isTranslationAvailable = + matchLocale( *(ui->languageWidget), selectedLocale, + [&](const QLocale& x){ return x.language() == defaultLocale.language() && x.country() == defaultLocale.country(); } ); + + if ( !isTranslationAvailable ) + { + cDebug() << "Matching approximate locale" << defaultLocale.language(); + + isTranslationAvailable = + matchLocale( *(ui->languageWidget), selectedLocale, + [&](const QLocale& x){ return x.language() == defaultLocale.language(); } ) ; + } + + if ( !isTranslationAvailable ) + { + QLocale en_us( QLocale::English, QLocale::UnitedStates ); + + cDebug() << "Matching English (US)"; + isTranslationAvailable = + matchLocale( *(ui->languageWidget), selectedLocale, + [&](const QLocale& x){ return x == en_us; } ); + + // Now, if it matched, because we didn't match the system locale, switch to the one found + if ( isTranslationAvailable ) + QLocale::setDefault( selectedLocale ); + } + + if ( isTranslationAvailable ) + CalamaresUtils::installTranslator( selectedLocale.name(), + Calamares::Branding::instance()->translationsPathPrefix(), + qApp ); + else + cWarning() << "No available translation matched" << defaultLocale; + + connect( ui->languageWidget, + static_cast< void ( QComboBox::* )( int ) >( &QComboBox::currentIndexChanged ), + this, + [&]( int newIndex ) + { + QLocale selectedLocale = ui->languageWidget->itemData( newIndex, Qt::UserRole ).toLocale(); + cDebug() << "Selected locale" << selectedLocale.name(); + + QLocale::setDefault( selectedLocale ); + CalamaresUtils::installTranslator( selectedLocale, + Calamares::Branding::instance()->translationsPathPrefix(), + qApp ); + } ); } From 6930400b67faaf7e19a9a373c1018e4594f64263 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Wed, 18 Apr 2018 11:34:09 -0400 Subject: [PATCH 2/8] DEBUG logging --- src/modules/welcome/WelcomePage.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/welcome/WelcomePage.cpp b/src/modules/welcome/WelcomePage.cpp index 8bee436e6..e35ec1d55 100644 --- a/src/modules/welcome/WelcomePage.cpp +++ b/src/modules/welcome/WelcomePage.cpp @@ -147,6 +147,10 @@ WelcomePage::initLanguages() QLocale thisLocale = QLocale( locale ); QString lang = QLocale::languageToString( thisLocale.language() ); + cDebug() << "LOCALE" << locale << "lang" << lang << "country" << QLocale::countryToString( thisLocale.country() ); + cDebug() << " .. countries=" << thisLocale.language() << QLocale::countriesForLanguage( thisLocale.language() ); + cDebug() << " .. " << thisLocale.nativeLanguageName() << thisLocale.nativeCountryName(); + if ( QLocale::countriesForLanguage( thisLocale.language() ).count() > 2 ) lang.append( QString( " (%1)" ) .arg( QLocale::countryToString( thisLocale.country() ) ) ); From 59537d86d65ffc190cd2f74587b2893161065c19 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Wed, 18 Apr 2018 18:18:00 -0400 Subject: [PATCH 3/8] [welcome] Present languages in native format - Introduce intermediate data class for building up the list of languages to present. - Sort on the English names, with en_US at the top (ugh). - Show the native names. --- src/modules/welcome/WelcomePage.cpp | 86 ++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 20 deletions(-) diff --git a/src/modules/welcome/WelcomePage.cpp b/src/modules/welcome/WelcomePage.cpp index e35ec1d55..eff0aeb47 100644 --- a/src/modules/welcome/WelcomePage.cpp +++ b/src/modules/welcome/WelcomePage.cpp @@ -132,39 +132,85 @@ bool matchLocale( QComboBox& list, QLocale& matchFound, std::function 2 ) + { + sortKey.append( QString( " (%1)" ) + .arg( QLocale::countryToString( thisLocale.country() ) ) ); + label.append( QString( " (%1)" ).arg( thisLocale.nativeCountryName() ) ); + } + + m_sortKey = sortKey; + m_label = label; + } + + QString m_locale; // the locale identifier, e.g. "en_GB" + QString m_sortKey; // the English name of the locale + QString m_label; // the native name of the locale + + /** @brief Define a sorting order. + * + * English (@see isEnglish() -- it means en_US) is sorted at the top. + */ + bool operator <(const LocaleLabel& other) const + { + if ( isEnglish() ) + return !other.isEnglish(); + if ( other.isEnglish() ) + return false; + return m_sortKey < other.m_sortKey; + } + + /** @brief Is this locale English? + * + * en_US and en (American English) is defined as English. The Queen's + * English -- proper English -- is relegated to non-English status. + */ + constexpr bool isEnglish() const + { + return m_locale == QLatin1Literal( "en_US" ) || m_locale == QLatin1Literal( "en" ); + } +} ; + void WelcomePage::initLanguages() { - ui->languageWidget->setInsertPolicy( QComboBox::InsertAlphabetically ); - - QLocale defaultLocale = QLocale( QLocale::system().name() ); - // Fill the list of translations + ui->languageWidget->clear(); + ui->languageWidget->setInsertPolicy( QComboBox::InsertAtBottom ); + { + std::list< LocaleLabel > localeList; const auto locales = QString( CALAMARES_TRANSLATION_LANGUAGES ).split( ';'); for ( const QString& locale : locales ) { - QLocale thisLocale = QLocale( locale ); - QString lang = QLocale::languageToString( thisLocale.language() ); + localeList.emplace_back( locale ); + } - cDebug() << "LOCALE" << locale << "lang" << lang << "country" << QLocale::countryToString( thisLocale.country() ); - cDebug() << " .. countries=" << thisLocale.language() << QLocale::countriesForLanguage( thisLocale.language() ); - cDebug() << " .. " << thisLocale.nativeLanguageName() << thisLocale.nativeCountryName(); + localeList.sort(); // According to the sortkey, which is english - if ( QLocale::countriesForLanguage( thisLocale.language() ).count() > 2 ) - lang.append( QString( " (%1)" ) - .arg( QLocale::countryToString( thisLocale.country() ) ) ); - - ui->languageWidget->addItem( lang, thisLocale ); + for ( const auto& locale : localeList ) + { + cDebug() << locale.m_locale << locale.m_sortKey; + ui->languageWidget->addItem( locale.m_label, QLocale( locale.m_locale ) ); } } // Find the best initial translation - QLocale selectedLocale; + QLocale defaultLocale = QLocale( QLocale::system().name() ); + QLocale matchedLocale; cDebug() << "Matching exact locale" << defaultLocale; bool isTranslationAvailable = - matchLocale( *(ui->languageWidget), selectedLocale, + matchLocale( *(ui->languageWidget), matchedLocale, [&](const QLocale& x){ return x.language() == defaultLocale.language() && x.country() == defaultLocale.country(); } ); if ( !isTranslationAvailable ) @@ -172,7 +218,7 @@ WelcomePage::initLanguages() cDebug() << "Matching approximate locale" << defaultLocale.language(); isTranslationAvailable = - matchLocale( *(ui->languageWidget), selectedLocale, + matchLocale( *(ui->languageWidget), matchedLocale, [&](const QLocale& x){ return x.language() == defaultLocale.language(); } ) ; } @@ -182,16 +228,16 @@ WelcomePage::initLanguages() cDebug() << "Matching English (US)"; isTranslationAvailable = - matchLocale( *(ui->languageWidget), selectedLocale, + matchLocale( *(ui->languageWidget), matchedLocale, [&](const QLocale& x){ return x == en_us; } ); // Now, if it matched, because we didn't match the system locale, switch to the one found if ( isTranslationAvailable ) - QLocale::setDefault( selectedLocale ); + QLocale::setDefault( matchedLocale ); } if ( isTranslationAvailable ) - CalamaresUtils::installTranslator( selectedLocale.name(), + CalamaresUtils::installTranslator( matchedLocale.name(), Calamares::Branding::instance()->translationsPathPrefix(), qApp ); else From 7c944760fc73772c1d117670269c1066149ad230 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 19 Apr 2018 07:19:10 -0400 Subject: [PATCH 4/8] [welcome] Only show (country) in list if the locale suggests it - A locale suggests it is country-specific by having the form _ - This mostly fixes locale "ar" being presented as "Arabiy (Misr)" when there is no need to (and the RTL is messed up then, too). --- src/modules/welcome/WelcomePage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/welcome/WelcomePage.cpp b/src/modules/welcome/WelcomePage.cpp index eff0aeb47..46041e063 100644 --- a/src/modules/welcome/WelcomePage.cpp +++ b/src/modules/welcome/WelcomePage.cpp @@ -141,7 +141,7 @@ struct LocaleLabel QString sortKey = QLocale::languageToString( thisLocale.language() ); QString label = thisLocale.nativeLanguageName(); - if ( QLocale::countriesForLanguage( thisLocale.language() ).count() > 2 ) + if ( locale.contains( '_' ) && QLocale::countriesForLanguage( thisLocale.language() ).count() > 2 ) { sortKey.append( QString( " (%1)" ) .arg( QLocale::countryToString( thisLocale.country() ) ) ); From 7cc2b222d93df33b65144c99b3c373b3f3bb111b Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 19 Apr 2018 07:30:54 -0400 Subject: [PATCH 5/8] [welcome] Present RTL (country) annotations better - The (RTL) text "Arabiy (Misr)" should be entirely RTL, so make the parenthetical insert -- which would otherwise be LTR and so mess up the placing of those parenthesis around the country -- explicitly RTL. - Since there are no RTL languages in Calamares right now with country-local translations, this isn't visible. --- src/modules/welcome/WelcomePage.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modules/welcome/WelcomePage.cpp b/src/modules/welcome/WelcomePage.cpp index 46041e063..48f5e5f1c 100644 --- a/src/modules/welcome/WelcomePage.cpp +++ b/src/modules/welcome/WelcomePage.cpp @@ -145,7 +145,11 @@ struct LocaleLabel { sortKey.append( QString( " (%1)" ) .arg( QLocale::countryToString( thisLocale.country() ) ) ); - label.append( QString( " (%1)" ).arg( thisLocale.nativeCountryName() ) ); + + // If the language name is RTL, make this parenthetical addition RTL as well. + QString countryFormat = label.isRightToLeft() ? QString( QChar( 0x202B ) ) : QString(); + countryFormat.append( QLatin1String( " (%1)" ) ); + label.append( countryFormat.arg( thisLocale.nativeCountryName() ) ); } m_sortKey = sortKey; From a9ffd3351da3bc94e52784b34fd1daa3a3e8078d Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 19 Apr 2018 08:40:04 -0400 Subject: [PATCH 6/8] [welcome] Support sr@latin - The QLocale constructor which takes a string (locale name) doesn't understand sr@latin, and returns the Cyrillic locale. Fix that by creating locales ourselves for @latin locales. - sr and sr@latin now display correctly in the right script in the native language dropdown. --- src/modules/welcome/WelcomePage.cpp | 36 +++++++++++++++++++---------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/modules/welcome/WelcomePage.cpp b/src/modules/welcome/WelcomePage.cpp index 48f5e5f1c..95569318d 100644 --- a/src/modules/welcome/WelcomePage.cpp +++ b/src/modules/welcome/WelcomePage.cpp @@ -135,28 +135,29 @@ bool matchLocale( QComboBox& list, QLocale& matchFound, std::function 2 ) + if ( locale.contains( '_' ) && QLocale::countriesForLanguage( m_locale.language() ).count() > 2 ) { sortKey.append( QString( " (%1)" ) - .arg( QLocale::countryToString( thisLocale.country() ) ) ); + .arg( QLocale::countryToString( m_locale.country() ) ) ); // If the language name is RTL, make this parenthetical addition RTL as well. QString countryFormat = label.isRightToLeft() ? QString( QChar( 0x202B ) ) : QString(); countryFormat.append( QLatin1String( " (%1)" ) ); - label.append( countryFormat.arg( thisLocale.nativeCountryName() ) ); + label.append( countryFormat.arg( m_locale.nativeCountryName() ) ); } m_sortKey = sortKey; m_label = label; } - QString m_locale; // the locale identifier, e.g. "en_GB" + QLocale m_locale; + QString m_localeId; // the locale identifier, e.g. "en_GB" QString m_sortKey; // the English name of the locale QString m_label; // the native name of the locale @@ -180,7 +181,18 @@ struct LocaleLabel */ constexpr bool isEnglish() const { - return m_locale == QLatin1Literal( "en_US" ) || m_locale == QLatin1Literal( "en" ); + return m_localeId == QLatin1Literal( "en_US" ) || m_localeId == QLatin1Literal( "en" ); + } + + static QLocale getLocale( const QString& localeName ) + { + if ( localeName.contains( "@latin" ) ) + { + QLocale loc( localeName ); + return QLocale( loc.language(), QLocale::Script::LatinScript, loc.country() ); + } + else + return QLocale( localeName ); } } ; @@ -203,8 +215,8 @@ WelcomePage::initLanguages() for ( const auto& locale : localeList ) { - cDebug() << locale.m_locale << locale.m_sortKey; - ui->languageWidget->addItem( locale.m_label, QLocale( locale.m_locale ) ); + cDebug() << locale.m_localeId << locale.m_sortKey; + ui->languageWidget->addItem( locale.m_label, locale.m_locale ); } } @@ -253,7 +265,7 @@ WelcomePage::initLanguages() [&]( int newIndex ) { QLocale selectedLocale = ui->languageWidget->itemData( newIndex, Qt::UserRole ).toLocale(); - cDebug() << "Selected locale" << selectedLocale.name(); + cDebug() << "Selected locale" << selectedLocale; QLocale::setDefault( selectedLocale ); CalamaresUtils::installTranslator( selectedLocale, From b9ed96d4f82e76cfa4595dbad05ebd4fa6c02934 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 19 Apr 2018 09:04:29 -0400 Subject: [PATCH 7/8] [libcalamares] Special case sr@latin QLocale::name() doesn't include script information, and if it did it would probably use sr_RS@Latin; when searching for translation files it won't consider dropping just the country. --- src/libcalamares/utils/CalamaresUtils.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/libcalamares/utils/CalamaresUtils.cpp b/src/libcalamares/utils/CalamaresUtils.cpp index fe07c62a0..14228ece0 100644 --- a/src/libcalamares/utils/CalamaresUtils.cpp +++ b/src/libcalamares/utils/CalamaresUtils.cpp @@ -147,6 +147,12 @@ installTranslator( const QLocale& locale, if ( localeName == "C" ) localeName = "en"; + // Special case of sr@latin + if ( locale.language() == QLocale::Language::Serbian && locale.script() == QLocale::Script::LatinScript ) + localeName = QStringLiteral( "sr@latin" ); + + cDebug() << "Looking for translations for" << localeName; + QTranslator* translator = nullptr; // Branding translations @@ -167,11 +173,11 @@ installTranslator( const QLocale& locale, "_", brandingTranslationsDir.absolutePath() ) ) { - cDebug() << "Translation: Branding using locale:" << localeName; + cDebug() << " .. Branding using locale:" << localeName; } else { - cDebug() << "Translation: Branding using default, system locale not found:" << localeName; + cDebug() << " .. Branding using default, system locale not found:" << localeName; translator->load( brandingTranslationsPrefix + "en" ); } @@ -190,11 +196,11 @@ installTranslator( const QLocale& locale, translator = new QTranslator( parent ); if ( translator->load( QString( ":/lang/calamares_" ) + localeName ) ) { - cDebug() << "Translation: Calamares using locale:" << localeName; + cDebug() << " .. Calamares using locale:" << localeName; } else { - cDebug() << "Translation: Calamares using default, system locale not found:" << localeName; + cDebug() << " .. Calamares using default, system locale not found:" << localeName; translator->load( QString( ":/lang/calamares_en" ) ); } From a47b3f8d14ebeb31132a98c3007368fbab07a325 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Thu, 19 Apr 2018 09:10:13 -0400 Subject: [PATCH 8/8] [libcalamares] Document special-case translations --- CMakeLists.txt | 6 ++++++ src/libcalamares/utils/CalamaresUtils.cpp | 2 ++ 2 files changed, 8 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index d74148978..e8075c857 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -195,6 +195,12 @@ endif() # Language en (source language) is added later. It isn't listed in # Transifex either. Get the list of languages and their status # from https://transifex.com/calamares/calamares/ . +# +# When adding a new language, take care that it is properly loaded +# by the translation framework. Languages with alternate scripts +# (sr@latin in particular) may need special handling in CalamaresUtils.cpp. +# +# TODO: pl and pl_PL overlap set( _tx_complete ca zh_CN zh_TW hr cs_CZ da fr lt pt_BR pt_PT es tr_TR) set( _tx_good sq ja pl sk ro it_IT hu he ru id de nl ) set( _tx_ok bg uk ast is ar sv el es_MX gl en_GB es_ES th fi_FI hi pl_PL diff --git a/src/libcalamares/utils/CalamaresUtils.cpp b/src/libcalamares/utils/CalamaresUtils.cpp index 14228ece0..dde3f9a13 100644 --- a/src/libcalamares/utils/CalamaresUtils.cpp +++ b/src/libcalamares/utils/CalamaresUtils.cpp @@ -148,6 +148,8 @@ installTranslator( const QLocale& locale, localeName = "en"; // Special case of sr@latin + // + // See top-level CMakeLists.txt about special cases for translation loading. if ( locale.language() == QLocale::Language::Serbian && locale.script() == QLocale::Script::LatinScript ) localeName = QStringLiteral( "sr@latin" );