You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
157 lines
5.0 KiB
C++
157 lines
5.0 KiB
C++
#include "languageenvironment.h"
|
|
|
|
#include <QFile>
|
|
#include <QLocale>
|
|
#include <QProcess>
|
|
#include <QSettings>
|
|
|
|
#include "preferences.h"
|
|
|
|
#include <locale.h>
|
|
#include <langinfo.h>
|
|
|
|
static QString posixLanguage(const QString &code)
|
|
{
|
|
const QLocale locale(code);
|
|
if (locale.language() == QLocale::C)
|
|
return code;
|
|
|
|
QString name = locale.name();
|
|
const QLocale regionalLocale(name);
|
|
if (locale.script() != regionalLocale.script()) {
|
|
const QString script = QLocale::scriptToString(locale.script()).toLower();
|
|
if (script == "latin" || script == "cyrillic" || script == "devanagari")
|
|
name += '@' + script;
|
|
}
|
|
return name;
|
|
}
|
|
|
|
static QString utf8Locale(QString name)
|
|
{
|
|
const int modifier = name.indexOf('@');
|
|
name.insert(modifier < 0 ? name.size() : modifier, ".UTF-8");
|
|
return name;
|
|
}
|
|
|
|
static bool isAvailable(const QString &name)
|
|
{
|
|
locale_t locale = newlocale(LC_ALL_MASK, name.toUtf8().constData(), nullptr);
|
|
if (!locale)
|
|
return false;
|
|
freelocale(locale);
|
|
return true;
|
|
}
|
|
|
|
static bool isUtf8Locale(const QString &name)
|
|
{
|
|
locale_t locale = newlocale(LC_ALL_MASK, name.toUtf8().constData(), nullptr);
|
|
if (!locale)
|
|
return false;
|
|
const QByteArray encoding = QByteArray(nl_langinfo_l(CODESET, locale)).toUpper();
|
|
freelocale(locale);
|
|
return encoding == "UTF-8" || encoding == "UTF8";
|
|
}
|
|
|
|
static QString fallbackLocale()
|
|
{
|
|
if (isAvailable("en_US.UTF-8"))
|
|
return QStringLiteral("en_US.UTF-8");
|
|
|
|
static const QString installedLocale = []() {
|
|
QProcess process;
|
|
process.start("locale", {"-a"});
|
|
if (process.waitForFinished(1000)) {
|
|
for (const QByteArray &line : process.readAllStandardOutput().split('\n')) {
|
|
const QString name = QString::fromLocal8Bit(line.trimmed());
|
|
const QString normalized = QString(name).remove('-').toLower();
|
|
if (!normalized.startsWith("c.") && normalized.contains("utf8") && isAvailable(name))
|
|
return name;
|
|
}
|
|
}
|
|
return QStringLiteral("C.UTF-8");
|
|
}();
|
|
return installedLocale;
|
|
}
|
|
|
|
QString LanguageEnvironment::systemLanguage()
|
|
{
|
|
QString language;
|
|
QFile file("/etc/locale.gen");
|
|
if (file.open(QIODevice::ReadOnly)) {
|
|
const QStringList lines = QString::fromUtf8(file.readAll()).split('\n');
|
|
for (const QString &line : lines) {
|
|
if (line.startsWith('#') || line.trimmed().isEmpty())
|
|
continue;
|
|
|
|
language = line.split(' ').first().split('.').first();
|
|
}
|
|
}
|
|
return language.isEmpty() ? QStringLiteral("en_US") : language;
|
|
}
|
|
|
|
QStringList LanguageEnvironment::preferredLanguages()
|
|
{
|
|
QSettings settings(Preferences::file(QStringLiteral("services")), QSettings::IniFormat);
|
|
settings.beginGroup(QStringLiteral("Language"));
|
|
const QString primary = settings.value("language").toString();
|
|
if (primary.isEmpty())
|
|
return {systemLanguage()};
|
|
QStringList codes = settings.value("preferredLanguages").toStringList();
|
|
codes.removeAll(QString());
|
|
codes.removeAll(primary);
|
|
codes.prepend(primary);
|
|
codes.removeDuplicates();
|
|
return codes;
|
|
}
|
|
|
|
QStringList LanguageEnvironment::translationLanguages(const QStringList &codes)
|
|
{
|
|
QStringList languages;
|
|
for (const QString &code : codes) {
|
|
if (code.isEmpty())
|
|
continue;
|
|
languages.append(posixLanguage(code));
|
|
}
|
|
languages.removeDuplicates();
|
|
return languages;
|
|
}
|
|
|
|
void LanguageEnvironment::apply(QProcessEnvironment &environment, const QStringList &codes)
|
|
{
|
|
const QStringList languages = translationLanguages(codes);
|
|
if (languages.isEmpty())
|
|
return;
|
|
|
|
QString localeName = utf8Locale(languages.first());
|
|
if (!isAvailable(localeName)) {
|
|
// Gettext needs a valid non-C message locale even when its language packs are installed.
|
|
localeName = fallbackLocale();
|
|
}
|
|
environment.remove("LC_ALL");
|
|
for (const QString &name : environment.keys()) {
|
|
if (name.startsWith("LC_") && !environment.value(name).isEmpty()
|
|
&& !isAvailable(environment.value(name))) {
|
|
environment.insert(name, localeName);
|
|
}
|
|
}
|
|
// LC_CTYPE overrides LANG for Qt and Fontconfig, including valid but non-UTF-8 values like C.
|
|
if (!environment.value("LC_CTYPE").isEmpty() && !isUtf8Locale(environment.value("LC_CTYPE")))
|
|
environment.insert("LC_CTYPE", localeName);
|
|
environment.insert("LANG", localeName);
|
|
environment.insert("LC_MESSAGES", localeName);
|
|
|
|
// Gettext has no English catalogs to stop at, so languages after English would replace the source strings.
|
|
QStringList messageLanguages;
|
|
for (const QString &language : languages) {
|
|
messageLanguages.append(language);
|
|
if (language == "en" || language.startsWith("en_") || language.startsWith("en@"))
|
|
break;
|
|
}
|
|
environment.insert("LANGUAGE", messageLanguages.join(':'));
|
|
}
|
|
|
|
void LanguageEnvironment::apply(QProcessEnvironment &environment)
|
|
{
|
|
apply(environment, preferredLanguages());
|
|
}
|