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) {
101 d->locales.push_back(l);
103 qCWarning(C_LANGSELECT) <<
"Can not add invalid locale" << l <<
"to the list of supported locales.";
112 d->locales.reserve(locales.
size());
113 for (
const QString &l : locales) {
116 d->locales.push_back(locale);
118 qCWarning(C_LANGSELECT,
"Can not add invalid locale \"%s\" to the list of supported locales.", qUtf8Printable(l));
127 d->locales.push_back(locale);
129 qCWarning(C_LANGSELECT) <<
"Can not add invalid locale" << locale <<
"to the list of supported locales.";
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;
153 const QString filter = name + _pref + u
'*' + _suff;
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());
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 empty path or name.");
190 const QDir dir(path);
191 if (Q_LIKELY(dir.
exists())) {
193 if (Q_LIKELY(!dirs.empty())) {
194 d->locales.
reserve(dirs.size());
195 bool shrinkToFit =
false;
196 for (
const QString &subDir : dirs) {
197 const QString relFn = subDir + u
'/' + name;
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());
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;
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);
463 uri.setPath(pathParts.join(u
'/'));
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
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;
584 if (domainParts.size() > 2) {
585 const QLocale l(domainParts.at(0));
587 qCDebug(C_LANGSELECT) <<
"Found supported locale" << l <<
"in subdomain of domain" << domain;
592 qCDebug(C_LANGSELECT) <<
"Can not find supported locale for subdomain" << domain;
598 const auto domain = c->req()->uri().
host();
601 if (domain.endsWith(i.key())) {
602 qCDebug(C_LANGSELECT) <<
"Found valid locale" << i.
value() <<
"in domain map for domain" << domain;
610 if (domainParts.size() > 1) {
611 const QLocale l(domainParts.at(domainParts.size() - 1));
613 qCDebug(C_LANGSELECT) <<
"Found supported locale" << l <<
"in domain" << domain;
618 qCDebug(C_LANGSELECT) <<
"Can not find supported locale for domain" << domain;
622 bool LangSelectPrivate::getFromHeader(
Context *c,
const QString &name)
const
624 if (detectFromHeader) {
626 if (Q_LIKELY(!accpetedLangs.empty())) {
627 std::map<float,QLocale> langMap;
628 for (
const QString &al : accpetedLangs) {
629 const auto idx = al.
indexOf(u
';');
630 float priority = 1.0f;
634 langPart = al.
left(idx);
636 priority = ref.
mid(ref.indexOf(u
'=') + 1).toFloat(&ok);
642 const auto search = langMap.find(priority);
643 if (search == langMap.cend()) {
644 langMap.insert({priority, locale});
648 if (!langMap.empty()) {
649 auto i = langMap.crbegin();
650 while (i != langMap.crend()) {
651 if (locales.contains(i->second)) {
653 qCDebug(C_LANGSELECT) <<
"Selected locale" << c->
locale() <<
"from" << name <<
"header";
661 const auto constLocales = locales;
662 while (i != langMap.crend()) {
663 for (
const QLocale &l : constLocales) {
664 if (l.
language() == i->second.language()) {
666 qCDebug(C_LANGSELECT) <<
"Selected locale" << c->
locale() <<
"from" << name <<
"header";
679 void LangSelectPrivate::setToQuery(
Context *c,
const QString &key)
const
681 auto uri = c->req()->uri();
683 if (query.hasQueryItem(key)) {
684 query.removeQueryItem(key);
688 qCDebug(C_LANGSELECT) <<
"Storing selected locale in URL query by redirecting to" << uri;
692 void LangSelectPrivate::setToCookie(
Context *c,
const QString &name)
const
694 qCDebug(C_LANGSELECT) <<
"Storing selected locale in cookie with name" << name;
696 #if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
697 cookie.setSameSitePolicy(QNetworkCookie::SameSite::Lax);
702 void LangSelectPrivate::setToSession(
Context *c,
const QString &key)
const
704 qCDebug(C_LANGSELECT) <<
"Storing selected locale in session key" << key;
708 void LangSelectPrivate::setFallback(
Context *c)
const
710 qCDebug(C_LANGSELECT) <<
"Can not find fitting locale, using fallback locale" << fallbackLocale;
714 void LangSelectPrivate::setContentLanguage(
Context *c)
const
716 if (addContentLanguageHeader) {
720 {langStashKey, c->
locale().bcp47Name()},
721 {dirStashKey, (c->
locale().textDirection() ==
Qt::LeftToRight ? QStringLiteral(
"ltr") : QStringLiteral(
"rtl"))}
725 void LangSelectPrivate::beforePrepareAction(
Context *c,
bool *skipMethod)
const
731 if (!c->
stash(SELECTION_TRIED).isNull()) {
735 detectLocale(c, source, skipMethod);
740 void LangSelectPrivate::_q_postFork(
Application *app)
745 #include "moc_langselect.cpp"
The Cutelyst Application.
void beforePrepareAction(Cutelyst::Context *c, bool *skipMethod)
T plugin()
Returns the registered plugin that casts to the template type T.
void postForked(Cutelyst::Application *app)
void stash(const QVariantHash &unite)
void detach(Action *action=nullptr)
QLocale locale() const noexcept
Response * res() const noexcept
void setStash(const QString &key, const QVariant &value)
void setLocale(const QLocale &locale)
Language selection plugin.
void setLocalesFromDir(const QString &path, const QString &name, const QString &prefix=QStringLiteral("."), const QString &suffix=QStringLiteral(".qm"))
void setDetectFromHeader(bool enabled)
void setLanguageDirStashKey(const QString &key=QStringLiteral("c_langselect_dir"))
static bool fromPath(Context *c, const QString &locale)
static QVector< QLocale > getSupportedLocales()
void setFallbackLocale(const QLocale &fallback)
void setQueryKey(const QString &key)
void setSubDomainMap(const QMap< QString, QLocale > &map)
static bool fromCookie(Context *c, const QString &name=QString())
static bool fromDomain(Context *c, const QMap< QString, QLocale > &domainMap=QMap< QString, QLocale >())
void setDomainMap(const QMap< QString, QLocale > &map)
void setLanguageCodeStashKey(const QString &key=QStringLiteral("c_langselect_lang"))
static bool fromUrlQuery(Context *c, const QString &key=QString())
void setCookieName(const QString &name)
static bool fromSession(Context *c, const QString &key=QString())
void setLocalesFromDirs(const QString &path, const QString &name)
static bool fromSubDomain(Context *c, const QMap< QString, QLocale > &subDomainMap=QMap< QString, QLocale >())
QVector< QLocale > supportedLocales() const
virtual ~LangSelect() override
void setSessionKey(const QString &key)
void addSupportedLocale(const QLocale &locale)
virtual bool setup(Application *app) override
void setSupportedLocales(const QVector< QLocale > &locales)
LangSelect(Application *parent, Source source)
QString header(const QString &key) const
QString queryParam(const QString &key, const QString &defaultValue={}) const
QString cookie(const QString &name) const
void redirect(const QUrl &url, quint16 status=Found)
void setHeader(const QString &field, const QString &value)
void setCookie(const QNetworkCookie &cookie)
static QVariant value(Context *c, const QString &key, const QVariant &defaultValue=QVariant())
static void setValue(Context *c, const QString &key, const QVariant &value)
The Cutelyst namespace holds all public Cutelyst API.
QFileInfoList entryInfoList(QDir::Filters filters, QDir::SortFlags sort) const const
QStringList entryList(QDir::Filters filters, QDir::SortFlags sort) const const
bool exists() const const
QString bcp47Name() const const
QLocale::Language language() const const
const T & value() const const
QMap::const_iterator constBegin() const const
QMap::const_iterator constEnd() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QString::const_reverse_iterator crbegin() const const
bool isEmpty() const const
QString left(int n) const const
QByteArray toLatin1() const const
QString toLower() const const
int indexOf(QStringView str, int from) const const
QStringView mid(qsizetype start) const const
QString host(QUrl::ComponentFormattingOptions options) const const
QString path(QUrl::ComponentFormattingOptions options) const const
QLocale toLocale() const const