2020-08-25 16:05:56 +02:00
|
|
|
/* === This file is part of Calamares - <https://calamares.io> ===
|
2016-08-10 11:40:22 +02:00
|
|
|
*
|
2020-08-22 01:19:58 +02:00
|
|
|
* SPDX-FileCopyrightText: 2016 Teo Mrnjavac <teo@kde.org>
|
|
|
|
* SPDX-FileCopyrightText: 2017-2019 Adriaan de Groot <groot@kde.org>
|
|
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
2016-08-10 11:40:22 +02:00
|
|
|
*
|
2020-08-25 16:05:56 +02:00
|
|
|
* Calamares is Free Software: see the License-Identifier above.
|
2016-08-10 11:40:22 +02:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "LocaleConfiguration.h"
|
2020-01-28 17:35:54 +01:00
|
|
|
|
|
|
|
#include "utils/Logger.h"
|
|
|
|
|
2016-12-01 11:59:44 +01:00
|
|
|
#include <QLocale>
|
2020-01-28 17:35:54 +01:00
|
|
|
#include <QRegularExpression>
|
2016-08-10 11:40:22 +02:00
|
|
|
|
|
|
|
LocaleConfiguration::LocaleConfiguration()
|
2017-06-06 16:45:35 +02:00
|
|
|
: explicit_lang( false )
|
|
|
|
, explicit_lc( false )
|
2016-08-10 11:40:22 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-01-08 13:23:16 +01:00
|
|
|
LocaleConfiguration::LocaleConfiguration( const QString& localeName, const QString& formatsName )
|
2019-01-08 11:10:16 +01:00
|
|
|
: LocaleConfiguration()
|
2016-08-10 11:40:22 +02:00
|
|
|
{
|
2019-09-07 16:58:37 +02:00
|
|
|
lc_numeric = lc_time = lc_monetary = lc_paper = lc_name = lc_address = lc_telephone = lc_measurement
|
|
|
|
= lc_identification = formatsName;
|
2019-01-08 11:39:15 +01:00
|
|
|
|
2020-07-21 11:08:17 +02:00
|
|
|
setLanguage( localeName );
|
2019-01-08 11:39:15 +01:00
|
|
|
}
|
|
|
|
|
2019-01-08 13:23:16 +01:00
|
|
|
|
2019-01-08 18:09:34 +01:00
|
|
|
void
|
2019-09-07 16:58:37 +02:00
|
|
|
LocaleConfiguration::setLanguage( const QString& localeName )
|
2019-01-08 11:39:15 +01:00
|
|
|
{
|
|
|
|
QString language = localeName.split( '_' ).first();
|
2019-01-08 18:09:34 +01:00
|
|
|
m_languageLocaleBcp47 = QLocale( language ).bcp47Name().toLower();
|
|
|
|
m_lang = localeName;
|
2016-08-10 11:40:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LocaleConfiguration
|
|
|
|
LocaleConfiguration::fromLanguageAndLocation( const QString& languageLocale,
|
|
|
|
const QStringList& availableLocales,
|
|
|
|
const QString& countryCode )
|
|
|
|
{
|
2019-01-08 13:23:16 +01:00
|
|
|
QString language = languageLocale.split( '_' ).first();
|
2016-08-10 11:40:22 +02:00
|
|
|
|
2020-01-28 17:35:54 +01:00
|
|
|
// Either an exact match, or the whole language part matches
|
|
|
|
// (followed by .<encoding> or _<country>
|
|
|
|
QStringList linesForLanguage = availableLocales.filter( QRegularExpression( language + "[._]" ) );
|
2016-08-10 11:40:22 +02:00
|
|
|
|
|
|
|
QString lang;
|
|
|
|
if ( linesForLanguage.length() == 0 || languageLocale.isEmpty() )
|
2019-09-07 16:58:37 +02:00
|
|
|
{
|
2016-08-10 16:10:06 +02:00
|
|
|
lang = "en_US.UTF-8";
|
2019-09-07 16:58:37 +02:00
|
|
|
}
|
2016-08-10 11:40:22 +02:00
|
|
|
else if ( linesForLanguage.length() == 1 )
|
2019-09-07 16:58:37 +02:00
|
|
|
{
|
2016-08-10 11:40:22 +02:00
|
|
|
lang = linesForLanguage.first();
|
2019-09-07 16:58:37 +02:00
|
|
|
}
|
2016-08-10 11:40:22 +02:00
|
|
|
|
|
|
|
// lang could still be empty if we found multiple locales that satisfy myLanguage
|
|
|
|
|
|
|
|
// The following block was inspired by Ubiquity, scripts/localechooser-apply.
|
|
|
|
// No copyright statement found in file, assuming GPL v2 or later.
|
|
|
|
/* # In the special cases of Portuguese and Chinese, selecting a
|
|
|
|
# different location may imply a different dialect of the language.
|
|
|
|
# In such cases, make LANG reflect the selected language (for
|
|
|
|
# messages, character types, and collation) and make the other
|
|
|
|
# locale categories reflect the selected location. */
|
|
|
|
if ( language == "pt" || language == "zh" )
|
|
|
|
{
|
2019-09-07 16:58:37 +02:00
|
|
|
QString proposedLocale = QString( "%1_%2" ).arg( language ).arg( countryCode );
|
2020-07-21 11:08:17 +02:00
|
|
|
for ( const QString& line : linesForLanguage )
|
2016-08-10 11:40:22 +02:00
|
|
|
{
|
|
|
|
if ( line.contains( proposedLocale ) )
|
|
|
|
{
|
|
|
|
lang = line;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we found no good way to set a default lang, do a search with the whole
|
|
|
|
// language locale and pick the first result, if any.
|
|
|
|
if ( lang.isEmpty() )
|
|
|
|
{
|
2019-09-07 16:58:37 +02:00
|
|
|
for ( const QString& line : availableLocales )
|
2016-08-10 11:40:22 +02:00
|
|
|
{
|
|
|
|
if ( line.startsWith( languageLocale ) )
|
|
|
|
{
|
|
|
|
lang = line;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Else we have an unrecognized or unsupported locale, all we can do is go with
|
|
|
|
// en_US.UTF-8 UTF-8. This completes all default language setting guesswork.
|
|
|
|
if ( lang.isEmpty() )
|
2019-09-07 16:58:37 +02:00
|
|
|
{
|
2016-08-10 16:10:06 +02:00
|
|
|
lang = "en_US.UTF-8";
|
2019-09-07 16:58:37 +02:00
|
|
|
}
|
2016-08-10 11:40:22 +02:00
|
|
|
|
|
|
|
|
|
|
|
// The following block was inspired by Ubiquity, scripts/localechooser-apply.
|
|
|
|
// No copyright statement found in file, assuming GPL v2 or later.
|
|
|
|
/* # It is relatively common for the combination of language and location (as
|
|
|
|
# selected on the timezone page) not to identify a supported combined
|
|
|
|
# locale. For example, this happens when the user is a migrant, or when
|
|
|
|
# they prefer to use a different language to interact with their computer
|
|
|
|
# because that language is better-supported.
|
|
|
|
#
|
|
|
|
# In such cases, we would like to be able to use a locale reflecting the
|
|
|
|
# selected language in LANG for messages, character types, and collation,
|
|
|
|
# and to make the other locale categories reflect the selected location.
|
|
|
|
# This means that we have to guess at a suitable locale for the selected
|
|
|
|
# location, and we do not want to ask yet another locale-related question.
|
|
|
|
# Nevertheless, some cases are ambiguous: a user who has asked for the
|
|
|
|
# English language and identifies their location as Switzerland will get
|
|
|
|
# different numeric representation depending on which Swiss locale we pick.
|
|
|
|
#
|
|
|
|
# The goal of identifying a reasonable default for migrants makes things
|
|
|
|
# easier: it is reasonable to default to French for France despite the
|
|
|
|
# existence of several minority languages there, because anyone who prefers
|
|
|
|
# those languages will probably already have selected them and won't arrive
|
|
|
|
# here. However, in some cases we're unsure, and in some cases we actively
|
|
|
|
# don't want to pick a "preferred" language: selecting either Greek or
|
|
|
|
# Turkish as the default language for migrants to Cyprus would probably
|
|
|
|
# offend somebody! In such cases we simply punt to the old behaviour of not
|
|
|
|
# setting up a locale reflecting the location, which is suboptimal but is at
|
|
|
|
# least unlikely to give offence.
|
|
|
|
#
|
|
|
|
# Our best shot at general criteria for selecting a default language in
|
|
|
|
# these circumstances are as follows:
|
|
|
|
#
|
|
|
|
# * Exclude special-purpose (e.g. en_DK) and artificial (e.g. la_AU,
|
|
|
|
# tlh_GB) locales.
|
|
|
|
# * If there is a language specific to or very strongly associated with the
|
|
|
|
# country in question, prefer it unless it has rather few native
|
|
|
|
# speakers.
|
|
|
|
# * Exclude minority languages that are relatively unlikely to be spoken by
|
|
|
|
# migrants who have not already selected them as their preferred language
|
|
|
|
# earlier in the installer.
|
|
|
|
# * If there is an official national language likely to be seen in print
|
|
|
|
# media, road signs, etc., then prefer that.
|
|
|
|
# * In cases of doubt, selecting no default language is safe. */
|
|
|
|
|
|
|
|
// We make a proposed locale based on the UI language and the timezone's country. There is no
|
|
|
|
// guarantee that this will be a valid, supported locale (often it won't).
|
|
|
|
QString lc_formats;
|
2019-09-07 16:58:37 +02:00
|
|
|
QString combined = QString( "%1_%2" ).arg( language ).arg( countryCode );
|
2016-08-10 11:40:22 +02:00
|
|
|
// We look up if it's a supported locale.
|
2019-09-07 16:58:37 +02:00
|
|
|
for ( const QString& line : availableLocales )
|
2016-08-10 11:40:22 +02:00
|
|
|
{
|
|
|
|
if ( line.startsWith( combined ) )
|
|
|
|
{
|
|
|
|
lang = line;
|
|
|
|
lc_formats = line;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( lc_formats.isEmpty() )
|
|
|
|
{
|
|
|
|
QStringList available;
|
2019-09-07 16:58:37 +02:00
|
|
|
for ( const QString& line : availableLocales )
|
2016-08-10 11:40:22 +02:00
|
|
|
{
|
|
|
|
if ( line.contains( QString( "_%1" ).arg( countryCode ) ) )
|
|
|
|
{
|
|
|
|
available.append( line );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
available.sort();
|
|
|
|
if ( available.count() == 1 )
|
|
|
|
{
|
|
|
|
lc_formats = available.first();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
QMap< QString, QString > countryToDefaultLanguage {
|
|
|
|
{ "AU", "en" },
|
|
|
|
{ "CN", "zh" },
|
|
|
|
{ "DE", "de" },
|
|
|
|
{ "DK", "da" },
|
|
|
|
{ "DZ", "ar" },
|
|
|
|
{ "ES", "es" },
|
|
|
|
// Somewhat unclear: Oromo has the greatest number of
|
|
|
|
// native speakers; English is the most widely spoken
|
|
|
|
// language and taught in secondary schools; Amharic is
|
|
|
|
// the official language and was taught in primary
|
|
|
|
// schools.
|
|
|
|
{ "ET", "am" },
|
|
|
|
{ "FI", "fi" },
|
|
|
|
{ "FR", "fr" },
|
|
|
|
{ "GB", "en" },
|
|
|
|
// Irish (Gaelic) is strongly associated with Ireland,
|
|
|
|
// but nearly all its native speakers also speak English,
|
|
|
|
// and migrants are likely to use English.
|
|
|
|
{ "IE", "en" },
|
2021-09-23 11:08:11 +02:00
|
|
|
// India has many languages even though Hindi is known as
|
|
|
|
// national language but English is used in all computer
|
|
|
|
// and mobile devices.
|
|
|
|
{ "IN", "en" },
|
2016-08-10 11:40:22 +02:00
|
|
|
{ "IT", "it" },
|
|
|
|
{ "MA", "ar" },
|
|
|
|
{ "MK", "mk" },
|
|
|
|
{ "NG", "en" },
|
|
|
|
{ "NL", "nl" },
|
|
|
|
{ "NZ", "en" },
|
|
|
|
{ "IL", "he" },
|
|
|
|
// Filipino is a de facto version of Tagalog, which is
|
|
|
|
// also spoken; English is also an official language.
|
|
|
|
{ "PH", "fil" },
|
|
|
|
{ "PK", "ur" },
|
|
|
|
{ "PL", "pl" },
|
|
|
|
{ "RU", "ru" },
|
|
|
|
// Chinese has more speakers, but English is the "common
|
|
|
|
// language of the nation" (Wikipedia) and official
|
|
|
|
// documents must be translated into English to be
|
|
|
|
// accepted.
|
|
|
|
{ "SG", "en" },
|
|
|
|
{ "SN", "wo" },
|
|
|
|
{ "TR", "tr" },
|
|
|
|
{ "TW", "zh" },
|
|
|
|
{ "UA", "uk" },
|
|
|
|
{ "US", "en" },
|
|
|
|
{ "ZM", "en" }
|
|
|
|
};
|
|
|
|
if ( countryToDefaultLanguage.contains( countryCode ) )
|
|
|
|
{
|
2019-09-07 16:58:37 +02:00
|
|
|
QString combinedLocale
|
|
|
|
= QString( "%1_%2" ).arg( countryToDefaultLanguage.value( countryCode ) ).arg( countryCode );
|
2016-08-10 11:40:22 +02:00
|
|
|
|
2019-09-07 16:58:37 +02:00
|
|
|
for ( const QString& line : availableLocales )
|
2016-08-10 11:40:22 +02:00
|
|
|
{
|
|
|
|
if ( line.startsWith( combinedLocale ) )
|
|
|
|
{
|
|
|
|
lc_formats = line;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we cannot make a good choice for a given country we go with the LANG
|
|
|
|
// setting, which defaults to en_US.UTF-8 UTF-8 if all else fails.
|
|
|
|
if ( lc_formats.isEmpty() )
|
2019-09-07 16:58:37 +02:00
|
|
|
{
|
2016-08-10 11:40:22 +02:00
|
|
|
lc_formats = lang;
|
2019-09-07 16:58:37 +02:00
|
|
|
}
|
2016-08-10 11:40:22 +02:00
|
|
|
|
2019-01-08 13:23:16 +01:00
|
|
|
return LocaleConfiguration( lang, lc_formats );
|
2016-08-10 11:40:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool
|
2017-08-08 14:59:44 +02:00
|
|
|
LocaleConfiguration::isEmpty() const
|
2016-08-10 11:40:22 +02:00
|
|
|
{
|
2019-09-07 16:58:37 +02:00
|
|
|
return m_lang.isEmpty() && lc_numeric.isEmpty() && lc_time.isEmpty() && lc_monetary.isEmpty() && lc_paper.isEmpty()
|
|
|
|
&& lc_name.isEmpty() && lc_address.isEmpty() && lc_telephone.isEmpty() && lc_measurement.isEmpty()
|
|
|
|
&& lc_identification.isEmpty();
|
2016-08-10 11:40:22 +02:00
|
|
|
}
|
|
|
|
|
2019-09-07 17:02:59 +02:00
|
|
|
/// @brief Sets @p value on @p key in the @p map if @p value is non-empty
|
|
|
|
static inline void
|
|
|
|
add_lc( QMap< QString, QString >& map, const char* key, const QString& value )
|
|
|
|
{
|
|
|
|
if ( !value.isEmpty() )
|
|
|
|
{
|
|
|
|
map.insert( key, value );
|
|
|
|
}
|
|
|
|
}
|
2016-08-10 11:40:22 +02:00
|
|
|
|
|
|
|
QMap< QString, QString >
|
2018-04-03 18:10:05 +02:00
|
|
|
LocaleConfiguration::toMap() const
|
2016-08-10 11:40:22 +02:00
|
|
|
{
|
|
|
|
QMap< QString, QString > map;
|
|
|
|
|
2019-09-07 17:02:59 +02:00
|
|
|
add_lc( map, "LANG", m_lang );
|
|
|
|
add_lc( map, "LC_NUMERIC", lc_numeric );
|
|
|
|
add_lc( map, "LC_TIME", lc_time );
|
|
|
|
add_lc( map, "LC_MONETARY", lc_monetary );
|
|
|
|
add_lc( map, "LC_PAPER", lc_paper );
|
|
|
|
add_lc( map, "LC_NAME", lc_name );
|
|
|
|
add_lc( map, "LC_ADDRESS", lc_address );
|
|
|
|
add_lc( map, "LC_TELEPHONE", lc_telephone );
|
|
|
|
add_lc( map, "LC_MEASUREMENT", lc_measurement );
|
|
|
|
add_lc( map, "LC_IDENTIFICATION", lc_identification );
|
2016-08-10 11:40:22 +02:00
|
|
|
|
|
|
|
return map;
|
|
|
|
}
|