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 fe07c62a0..dde3f9a13 100644 --- a/src/libcalamares/utils/CalamaresUtils.cpp +++ b/src/libcalamares/utils/CalamaresUtils.cpp @@ -147,6 +147,14 @@ installTranslator( const QLocale& locale, if ( localeName == "C" ) 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" ); + + cDebug() << "Looking for translations for" << localeName; + QTranslator* translator = nullptr; // Branding translations @@ -167,11 +175,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 +198,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" ) ); } diff --git a/src/modules/welcome/WelcomePage.cpp b/src/modules/welcome/WelcomePage.cpp index 5781b3a58..95569318d 100644 --- a/src/modules/welcome/WelcomePage.cpp +++ b/src/modules/welcome/WelcomePage.cpp @@ -106,90 +106,172 @@ WelcomePage::WelcomePage( RequirementsChecker* requirementsChecker, QWidget* par } -void -WelcomePage::initLanguages() +/** @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) { - ui->languageWidget->setInsertPolicy( QComboBox::InsertAlphabetically ); + 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; + } + } - QLocale defaultLocale = QLocale( QLocale::system().name() ); + return false; +} + +struct LocaleLabel +{ + LocaleLabel( const QString& locale ) + : m_locale( LocaleLabel::getLocale( locale ) ) + , m_localeId( locale ) { - bool isTranslationAvailable = false; + QString sortKey = QLocale::languageToString( m_locale.language() ); + QString label = m_locale.nativeLanguageName(); - const auto locales = QString( CALAMARES_TRANSLATION_LANGUAGES ).split( ';'); - for ( const QString& locale : locales ) + if ( locale.contains( '_' ) && QLocale::countriesForLanguage( m_locale.language() ).count() > 2 ) { - 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 ); - } + sortKey.append( QString( " (%1)" ) + .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( m_locale.nativeCountryName() ) ); } - if ( !isTranslationAvailable ) + m_sortKey = sortKey; + m_label = label; + } + + 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 + + /** @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_localeId == QLatin1Literal( "en_US" ) || m_localeId == QLatin1Literal( "en" ); + } + + static QLocale getLocale( const QString& localeName ) + { + if ( localeName.contains( "@latin" ) ) { - 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; - } - } + QLocale loc( localeName ); + return QLocale( loc.language(), QLocale::Script::LatinScript, loc.country() ); } + else + return QLocale( localeName ); + } +} ; - if ( !isTranslationAvailable ) +void +WelcomePage::initLanguages() +{ + // 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 ) { - 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; - } - } + localeList.emplace_back( locale ); } - if ( !isTranslationAvailable ) - cWarning() << "No available translation matched" << defaultLocale; + localeList.sort(); // According to the sortkey, which is english - connect( ui->languageWidget, - static_cast< void ( QComboBox::* )( int ) >( &QComboBox::currentIndexChanged ), - this, [ & ]( int newIndex ) + for ( const auto& locale : localeList ) { - QLocale selectedLocale = ui->languageWidget->itemData( newIndex, Qt::UserRole ).toLocale(); - cDebug() << "Selected locale" << selectedLocale.name(); + cDebug() << locale.m_localeId << locale.m_sortKey; + ui->languageWidget->addItem( locale.m_label, locale.m_locale ); + } + } - QLocale::setDefault( selectedLocale ); - CalamaresUtils::installTranslator( selectedLocale, - Calamares::Branding::instance()->translationsPathPrefix(), - qApp ); - } ); + // Find the best initial translation + QLocale defaultLocale = QLocale( QLocale::system().name() ); + QLocale matchedLocale; + + cDebug() << "Matching exact locale" << defaultLocale; + bool isTranslationAvailable = + matchLocale( *(ui->languageWidget), matchedLocale, + [&](const QLocale& x){ return x.language() == defaultLocale.language() && x.country() == defaultLocale.country(); } ); + + if ( !isTranslationAvailable ) + { + cDebug() << "Matching approximate locale" << defaultLocale.language(); + + isTranslationAvailable = + matchLocale( *(ui->languageWidget), matchedLocale, + [&](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), 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( matchedLocale ); } + + if ( isTranslationAvailable ) + CalamaresUtils::installTranslator( matchedLocale.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; + + QLocale::setDefault( selectedLocale ); + CalamaresUtils::installTranslator( selectedLocale, + Calamares::Branding::instance()->translationsPathPrefix(), + qApp ); + } ); }