6 #include "langselect_p.h" 8 #include <Cutelyst/Application> 9 #include <Cutelyst/Context> 10 #include <Cutelyst/Plugins/Session/Session> 11 #include <Cutelyst/Response> 15 #include <QLoggingCategory> 17 #include <QNetworkCookie> 23 Q_LOGGING_CATEGORY(C_LANGSELECT,
"cutelyst.plugin.langselect", QtWarningMsg)
29 #define SELECTION_TRIED QStringLiteral("_c_langselect_tried") 32 , d_ptr(new LangSelectPrivate)
40 , d_ptr(new LangSelectPrivate)
44 d->autoDetect =
false;
56 if (d->fallbackLocale.language() == QLocale::C) {
57 qCCritical(C_LANGSELECT,
"We need a valid fallback locale.");
62 if (d->source ==
URLQuery && d->queryKey.isEmpty()) {
63 qCCritical(C_LANGSELECT,
"Can not use url query as source with empty key name.");
65 }
else if (d->source ==
Session && d->sessionKey.isEmpty()) {
66 qCCritical(C_LANGSELECT,
"Can not use session as source with empty key name.");
68 }
else if (d->source ==
Cookie && d->cookieName.isEmpty()) {
69 qCCritical(C_LANGSELECT,
"Can not use cookie as source with empty cookie name.");
73 qCCritical(C_LANGSELECT,
"Invalid source.");
77 d->beforePrepareAction(c, skipMethod);
80 if (!d->locales.contains(d->fallbackLocale)) {
81 d->locales.append(d->fallbackLocale);
85 qCDebug(C_LANGSELECT) <<
"Initialized LangSelect plugin with the following settings:";
86 qCDebug(C_LANGSELECT) <<
"Supported locales:" << d->locales;
87 qCDebug(C_LANGSELECT) <<
"Fallback locale:" << d->fallbackLocale;
88 qCDebug(C_LANGSELECT) <<
"Auto detection source:" << d->source;
89 qCDebug(C_LANGSELECT) <<
"Detect from header:" << d->detectFromHeader;
98 d->locales.reserve(locales.
size());
99 for (
const QLocale &l : locales) {
100 if (Q_LIKELY(l.language() != QLocale::C)) {
101 d->locales.push_back(l);
103 qCWarning(C_LANGSELECT) <<
"Can not add invalid locale" << l <<
"to the list of suppored locales.";
112 d->locales.reserve(locales.
size());
113 for (
const QString &l : locales) {
115 if (Q_LIKELY(locale.
language() != QLocale::C)) {
116 d->locales.push_back(locale);
118 qCWarning(C_LANGSELECT,
"Can not add invalid locale \"%s\" to the list of supported locales.", qUtf8Printable(l));
125 if (Q_LIKELY(locale.
language() != QLocale::C)) {
127 d->locales.push_back(locale);
129 qCWarning(C_LANGSELECT) <<
"Can not add invalid locale" << locale <<
"to the list of supported locales.";
136 if (Q_LIKELY(l.
language() != QLocale::C)) {
138 d->locales.push_back(l);
140 qCWarning(C_LANGSELECT,
"Can not add invalid locale \"%s\" to the list of supported locales.", qUtf8Printable(locale));
149 const QDir dir(path);
150 if (Q_LIKELY(dir.
exists())) {
151 const auto _pref = prefix.
isEmpty() ? QStringLiteral(
".") : prefix;
152 const auto _suff = suffix.
isEmpty() ? QStringLiteral(
".qm") : suffix;
155 if (Q_LIKELY(!files.empty())) {
156 d->locales.reserve(files.size());
157 bool shrinkToFit =
false;
159 const auto fn = fi.fileName();
160 const auto prefIdx = fn.indexOf(_pref);
161 const auto locPart = fn.mid(prefIdx + _pref.length(), fn.length() - prefIdx - _suff.length() - _pref.length());
163 if (Q_LIKELY(l.
language() != QLocale::C)) {
164 d->locales.push_back(l);
165 qCDebug(C_LANGSELECT,
"Added locale \"%s\" to the list of supported locales.", qUtf8Printable(locPart));
168 qCWarning(C_LANGSELECT,
"Can not add invalid locale \"%s\" to the list of supported locales.", qUtf8Printable(locPart));
172 d->locales.squeeze();
175 qCWarning(C_LANGSELECT,
"Can not find translation files for \"%s\" in \"%s\".", qUtf8Printable(filter), qUtf8Printable(path));
178 qCWarning(C_LANGSELECT,
"Can not set locales from not existing directory \"%s\".", qUtf8Printable(path));
181 qCWarning(C_LANGSELECT,
"Can not set locales from dir with emtpy path or name.");
190 const QDir dir(path);
191 if (Q_LIKELY(dir.
exists())) {
192 const auto dirs = dir.
entryList(QDir::AllDirs);
193 if (Q_LIKELY(!dirs.empty())) {
194 d->locales.
reserve(dirs.size());
195 bool shrinkToFit =
false;
196 for (
const QString &subDir : dirs) {
200 if (Q_LIKELY(l.
language() != QLocale::C)) {
201 d->locales.push_back(l);
202 qCDebug(C_LANGSELECT,
"Added locale \"%s\" to the list of supported locales.", qUtf8Printable(subDir));
205 qCWarning(C_LANGSELECT,
"Can not add invalid locale \"%s\" to the list of supported locales.", qUtf8Printable(subDir));
212 d->locales.squeeze();
216 qCWarning(C_LANGSELECT,
"Can not set locales from not existing directory \"%s\".", qUtf8Printable(path));
219 qCWarning(C_LANGSELECT,
"Can not set locales from dirs with empty path or names.");
244 d->cookieName = name;
250 d->subDomainMap.clear();
252 d->locales.reserve(map.
size());
255 if (i.value().language() != QLocale::C) {
256 d->subDomainMap.insert(i.key(), i.value());
257 d->locales.append(i.value());
259 qCWarning(C_LANGSELECT) <<
"Can not add invalid locale" << i.
value() <<
"for subdomain" << i.key() <<
"to the subdomain map.";
263 d->locales.squeeze();
269 d->domainMap.clear();
271 d->locales.reserve(map.
size());
274 if (Q_LIKELY(i.value().language() != QLocale::C)) {
275 d->domainMap.insert(i.key(), i.value());
276 d->locales.append(i.value());
278 qCWarning(C_LANGSELECT) <<
"Can not add invalid locale" << i.
value() <<
"for domain" << i.key() <<
"to the domain map.";
282 d->locales.squeeze();
288 d->fallbackLocale = fallback;
294 d->detectFromHeader = enabled;
300 if (Q_LIKELY(!key.
isEmpty())) {
301 d->langStashKey = key;
303 qCWarning(C_LANGSELECT) <<
"Can not set an empty key name for the language code stash key. Using current key name" << d->langStashKey;
310 if (Q_LIKELY(!key.
isEmpty())) {
311 d->dirStashKey = key;
313 qCWarning(C_LANGSELECT) <<
"Can not set an empty key name for the language direction stash key. Using current key name" << d->dirStashKey;
320 qCCritical(C_LANGSELECT) <<
"LangSelect plugin not registered";
330 qCCritical(C_LANGSELECT) <<
"LangSelect plugin not registered";
334 const auto d = lsp->d_ptr;
335 const auto _key = !key.
isEmpty() ? key : d->queryKey;
336 if (!d->getFromQuery(c, _key)) {
337 if (!d->getFromHeader(c)) {
340 d->setToQuery(c, _key);
344 d->setContentLanguage(c);
351 bool foundInSession =
false;
354 qCCritical(C_LANGSELECT) <<
"LangSelect plugin not registered";
355 return foundInSession;
358 const auto d = lsp->d_ptr;
359 const auto _key = !key.
isEmpty() ? key : d->sessionKey;
360 foundInSession = d->getFromSession(c, _key);
361 if (!foundInSession) {
362 if (!d->getFromHeader(c)) {
365 d->setToSession(c, _key);
367 d->setContentLanguage(c);
369 return foundInSession;
374 bool foundInCookie =
false;
377 qCCritical(C_LANGSELECT) <<
"LangSelect plugin not registered";
378 return foundInCookie;
381 const auto d = lsp->d_ptr;
382 const auto _name = !name.
isEmpty() ? name : d->cookieName;
383 foundInCookie = d->getFromCookie(c, _name);
384 if (!foundInCookie) {
385 if (!d->getFromHeader(c)) {
388 d->setToCookie(c, _name);
390 d->setContentLanguage(c);
392 return foundInCookie;
397 bool foundInSubDomain =
false;
400 qCCritical(C_LANGSELECT) <<
"LangSelect plugin not registered";
401 return foundInSubDomain;
404 const auto d = lsp->d_ptr;
405 const auto _map = !subDomainMap.
empty() ? subDomainMap : d->subDomainMap;
406 foundInSubDomain = d->getFromSubdomain(c, _map);
407 if (!foundInSubDomain) {
408 if (!d->getFromHeader(c)) {
413 d->setContentLanguage(c);
415 return foundInSubDomain;
420 bool foundInDomain =
false;
423 qCCritical(C_LANGSELECT) <<
"LangSelect plugin not registered";
424 return foundInDomain;
427 const auto d = lsp->d_ptr;
428 const auto _map = !domainMap.
empty() ? domainMap : d->domainMap;
429 foundInDomain = d->getFromDomain(c, _map);
430 if (!foundInDomain) {
431 if (!d->getFromHeader(c)) {
436 d->setContentLanguage(c);
438 return foundInDomain;
444 qCCritical(C_LANGSELECT) <<
"LangSelect plugin not registered";
448 const auto d = lsp->d_ptr;
450 if (l.
language() != QLocale::C && d->locales.contains(l)) {
451 qCDebug(C_LANGSELECT) <<
"Found valid locale" << l <<
"in path";
453 d->setContentLanguage(c);
456 if (!d->getFromHeader(c)) {
459 auto uri = c->req()->uri();
461 const auto localeIdx = pathParts.
indexOf(locale);
464 qCDebug(C_LANGSELECT) <<
"Storing selected locale by redirecting to" << uri;
473 bool redirect =
false;
478 if (getFromSession(c, sessionKey)) {
482 if (getFromCookie(c, cookieName)) {
486 if (getFromQuery(c, queryKey)) {
490 if (getFromSubdomain(c, subDomainMap)) {
494 if (getFromDomain(c, domainMap)) {
510 if (foundIn != _source) {
512 setToSession(c, sessionKey);
514 setToCookie(c, cookieName);
516 setToQuery(c, queryKey);
525 setContentLanguage(c);
531 bool LangSelectPrivate::getFromQuery(
Context *c,
const QString &key)
const 534 if (l.language() != QLocale::C && locales.contains(l)) {
535 qCDebug(C_LANGSELECT) <<
"Found valid locale" << l <<
"in url query key" << key;
539 qCDebug(C_LANGSELECT) <<
"Can not find supported locale in url query key" << key;
544 bool LangSelectPrivate::getFromCookie(
Context *c,
const QString &cookie)
const 547 if (l.language() != QLocale::C && locales.contains(l)) {
548 qCDebug(C_LANGSELECT) <<
"Found valid locale" << l <<
"in cookie name" << cookie;
552 qCDebug(C_LANGSELECT) <<
"Can no find supported locale in cookie value with name" << cookie;
557 bool LangSelectPrivate::getFromSession(
Context *c,
const QString &key)
const 560 if (l.
language() != QLocale::C && locales.contains(l)) {
561 qCDebug(C_LANGSELECT) <<
"Found valid locale" << l <<
"in session key" << key;
565 qCDebug(C_LANGSELECT) <<
"Can not find supported locale in session value with key" << key;
572 const auto domain = c->req()->uri().
host();
575 if (domain.startsWith(i.key())) {
576 qCDebug(C_LANGSELECT) <<
"Found valid locale" << i.
value() <<
"in subdomain map for domain" << domain;
583 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) 584 const auto domainParts = domain.split(
QLatin1Char(
'.'), Qt::SkipEmptyParts);
586 const auto domainParts = domain.split(
QLatin1Char(
'.'), QString::SkipEmptyParts);
588 if (domainParts.size() > 2) {
589 const QLocale l(domainParts.at(0));
590 if (l.
language() != QLocale::C && locales.contains(l)) {
591 qCDebug(C_LANGSELECT) <<
"Found supported locale" << l <<
"in subdomain of domain" << domain;
596 qCDebug(C_LANGSELECT) <<
"Can not find supported locale for subdomain" << domain;
602 const auto domain = c->req()->uri().
host();
605 if (domain.endsWith(i.key())) {
606 qCDebug(C_LANGSELECT) <<
"Found valid locale" << i.
value() <<
"in domain map for domain" << domain;
613 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) 614 const auto domainParts = domain.split(
QLatin1Char(
'.'), Qt::SkipEmptyParts);
616 const auto domainParts = domain.split(
QLatin1Char(
'.'), QString::SkipEmptyParts);
618 if (domainParts.size() > 1) {
619 const QLocale l(domainParts.at(domainParts.size() - 1));
620 if (l.
language() != QLocale::C && locales.contains(l)) {
621 qCDebug(C_LANGSELECT) <<
"Found supported locale" << l <<
"in domain" << domain;
626 qCDebug(C_LANGSELECT) <<
"Can not find supported locale for domain" << domain;
630 bool LangSelectPrivate::getFromHeader(
Context *c,
const QString &name)
const 632 if (detectFromHeader) {
633 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) 638 if (Q_LIKELY(!accpetedLangs.empty())) {
639 std::map<float,QLocale> langMap;
640 for (
const QString &al : accpetedLangs) {
642 float priority = 1.0f;
646 langPart = al.
left(idx);
653 if (ok && locale.language() != QLocale::C) {
654 const auto search = langMap.find(priority);
655 if (search == langMap.cend()) {
656 langMap.insert({priority, locale});
660 if (!langMap.empty()) {
661 auto i = langMap.crbegin();
662 while (i != langMap.crend()) {
663 if (locales.contains(i->second)) {
665 qCDebug(C_LANGSELECT) <<
"Selected locale" << c->
locale() <<
"from" << name <<
"header";
673 const auto constLocales = locales;
674 while (i != langMap.crend()) {
675 for (
const QLocale &l : constLocales) {
676 if (l.
language() == i->second.language()) {
678 qCDebug(C_LANGSELECT) <<
"Selected locale" << c->
locale() <<
"from" << name <<
"header";
691 void LangSelectPrivate::setToQuery(
Context *c,
const QString &key)
const 693 auto uri = c->req()->uri();
695 if (query.hasQueryItem(key)) {
696 query.removeQueryItem(key);
700 qCDebug(C_LANGSELECT) <<
"Storing selected locale in URL query by redirecting to" << uri;
704 void LangSelectPrivate::setToCookie(
Context *c,
const QString &name)
const 706 qCDebug(C_LANGSELECT) <<
"Storing selected locale in cookie with name" << name;
710 void LangSelectPrivate::setToSession(
Context *c,
const QString &key)
const 712 qCDebug(C_LANGSELECT) <<
"Storing selected locale in session key" << key;
716 void LangSelectPrivate::setFallback(
Context *c)
const 718 qCDebug(C_LANGSELECT) <<
"Can not find fitting locale, using fallback locale" << fallbackLocale;
722 void LangSelectPrivate::setContentLanguage(
Context *c)
const 724 if (addContentLanguageHeader) {
729 {dirStashKey, (c->
locale().
textDirection() == Qt::LeftToRight ? QStringLiteral(
"ltr") : QStringLiteral(
"rtl"))}
733 void LangSelectPrivate::beforePrepareAction(
Context *c,
bool *skipMethod)
const 739 if (!c->
stash(SELECTION_TRIED).isNull()) {
743 detectLocale(c, source, skipMethod);
748 void LangSelectPrivate::_q_postFork(
Application *app)
753 #include "moc_langselect.cpp"
void setCookie(const QNetworkCookie &cookie)
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
void postForked(Cutelyst::Application *app)
void setHeader(const QString &field, const QString &value)
static bool fromPath(Context *c, const QString &locale)
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
Response * res() const noexcept
QMap::const_iterator constBegin() const const
QString host(QUrl::ComponentFormattingOptions options) const const
virtual ~LangSelect() override
void setStash(const QString &key, const QVariant &value)
void detach(Action *action=nullptr)
QString::const_reverse_iterator crbegin() const const
T plugin()
Returns the registered plugin that casts to the template type T.
QString queryParam(const QString &key, const QString &defaultValue={}) const
void setQueryKey(const QString &key)
QVector< QLocale > supportedLocales() const
void setSupportedLocales(const QVector< QLocale > &locales)
static bool fromCookie(Context *c, const QString &name=QString())
void setLanguageCodeStashKey(const QString &key=QStringLiteral("c_langselect_lang"))
bool exists() const const
void redirect(const QUrl &url, quint16 status=Found)
static bool fromUrlQuery(Context *c, const QString &key=QString())
void stash(const QVariantHash &unite)
void beforePrepareAction(Cutelyst::Context *c, bool *skipMethod)
QFileInfoList entryInfoList(QDir::Filters filters, QDir::SortFlags sort) const const
bool isEmpty() const const
void setLocalesFromDirs(const QString &path, const QString &name)
QMap::const_iterator constEnd() const const
Qt::LayoutDirection textDirection() const const
QString header(const QString &key) const
QString path(QUrl::ComponentFormattingOptions options) const const
QLocale::Language language() const const
const T & value() const const
static void setValue(Context *c, const QString &key, const QVariant &value)
The Cutelyst namespace holds all public Cutelyst API.
QLocale locale() const noexcept
void setLanguageDirStashKey(const QString &key=QStringLiteral("c_langselect_dir"))
QString toLower() const const
void addSupportedLocale(const QLocale &locale)
void setLocale(const QLocale &locale)
static bool fromDomain(Context *c, const QMap< QString, QLocale > &domainMap=QMap< QString, QLocale >())
Language selection plugin.
QByteArray toLatin1() const const
QString mid(int position, int n) const const
virtual bool setup(Application *app) override
static QVector< QLocale > getSupportedLocales()
void setCookieName(const QString &name)
QStringList entryList(QDir::Filters filters, QDir::SortFlags sort) const const
void setSubDomainMap(const QMap< QString, QLocale > &map)
void setDomainMap(const QMap< QString, QLocale > &map)
QString bcp47Name() const const
void setSessionKey(const QString &key)
LangSelect(Application *parent, Source source)
static QVariant value(Context *c, const QString &key, const QVariant &defaultValue=QVariant())
QString left(int n) const const
QLocale toLocale() const const
QString cookie(const QString &name) const
The Cutelyst Application.
static bool fromSession(Context *c, const QString &key=QString())
int indexOf(const QRegExp &rx, int from) const const
void setFallbackLocale(const QLocale &fallback)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
static bool fromSubDomain(Context *c, const QMap< QString, QLocale > &subDomainMap=QMap< QString, QLocale >())
void setDetectFromHeader(bool enabled)
void setLocalesFromDir(const QString &path, const QString &name, const QString &prefix=QStringLiteral("."), const QString &suffix=QStringLiteral(".qm"))