cutelyst  3.9.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
langselect.cpp
1 /*
2  * SPDX-FileCopyrightText: (C) 2018-2022 Matthias Fehring <mf@huessenbergnetz.de>
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 
6 #include "langselect_p.h"
7 
8 #include <Cutelyst/Application>
9 #include <Cutelyst/Context>
10 #include <Cutelyst/Plugins/Session/Session>
11 #include <Cutelyst/Response>
12 #include <map>
13 #include <utility>
14 
15 #include <QDir>
16 #include <QFileInfo>
17 #include <QLoggingCategory>
18 #include <QNetworkCookie>
19 #include <QUrl>
20 #include <QUrlQuery>
21 
22 Q_LOGGING_CATEGORY(C_LANGSELECT, "cutelyst.plugin.langselect", QtWarningMsg)
23 
24 using namespace Cutelyst;
25 
26 static thread_local LangSelect *lsp = nullptr;
27 
28 #define SELECTION_TRIED QStringLiteral("_c_langselect_tried")
29 
31  : Plugin(parent)
32  , d_ptr(new LangSelectPrivate)
33 {
34  Q_D(LangSelect);
35  d->source = source;
36  d->autoDetect = true;
37 }
38 
40  : Plugin(parent)
41  , d_ptr(new LangSelectPrivate)
42 {
43  Q_D(LangSelect);
44  d->source = AcceptHeader;
45  d->autoDetect = false;
46 }
47 
49 {
50  delete d_ptr;
51 }
52 
54 {
55  Q_D(LangSelect);
56  if (d->fallbackLocale.language() == QLocale::C) {
57  qCCritical(C_LANGSELECT, "We need a valid fallback locale.");
58  return false;
59  }
60  if (d->autoDetect) {
61  if (d->source < Fallback) {
62  if (d->source == URLQuery && d->queryKey.isEmpty()) {
63  qCCritical(C_LANGSELECT, "Can not use url query as source with empty key name.");
64  return false;
65  } else if (d->source == Session && d->sessionKey.isEmpty()) {
66  qCCritical(C_LANGSELECT, "Can not use session as source with empty key name.");
67  return false;
68  } else if (d->source == Cookie && d->cookieName.isEmpty()) {
69  qCCritical(C_LANGSELECT, "Can not use cookie as source with empty cookie name.");
70  return false;
71  }
72  } else {
73  qCCritical(C_LANGSELECT, "Invalid source.");
74  return false;
75  }
76  connect(app, &Application::beforePrepareAction, this, [d](Context *c, bool *skipMethod) {
77  d->beforePrepareAction(c, skipMethod);
78  });
79  }
80  if (!d->locales.contains(d->fallbackLocale)) {
81  d->locales.append(d->fallbackLocale);
82  }
83  connect(app, &Application::postForked, this, &LangSelectPrivate::_q_postFork);
84 
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;
90 
91  return true;
92 }
93 
95 {
96  Q_D(LangSelect);
97  d->locales.clear();
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);
102  } else {
103  qCWarning(C_LANGSELECT)
104  << "Can not add invalid locale" << l << "to the list of supported locales.";
105  }
106  }
107 }
108 
110 {
111  Q_D(LangSelect);
112  d->locales.clear();
113  d->locales.reserve(locales.size());
114  for (const QString &l : locales) {
115  QLocale locale(l);
116  if (Q_LIKELY(locale.language() != QLocale::C)) {
117  d->locales.push_back(locale);
118  } else {
119  qCWarning(C_LANGSELECT,
120  "Can not add invalid locale \"%s\" to the list of supported locales.",
121  qUtf8Printable(l));
122  }
123  }
124 }
125 
127 {
128  if (Q_LIKELY(locale.language() != QLocale::C)) {
129  Q_D(LangSelect);
130  d->locales.push_back(locale);
131  } else {
132  qCWarning(C_LANGSELECT) << "Can not add invalid locale" << locale
133  << "to the list of supported locales.";
134  }
135 }
136 
138 {
139  QLocale l(locale);
140  if (Q_LIKELY(l.language() != QLocale::C)) {
141  Q_D(LangSelect);
142  d->locales.push_back(l);
143  } else {
144  qCWarning(C_LANGSELECT,
145  "Can not add invalid locale \"%s\" to the list of supported locales.",
146  qUtf8Printable(locale));
147  }
148 }
149 
151  const QString &name,
152  const QString &prefix,
153  const QString &suffix)
154 {
155  Q_D(LangSelect);
156  d->locales.clear();
157  if (Q_LIKELY(!path.isEmpty() && !name.isEmpty())) {
158  const QDir dir(path);
159  if (Q_LIKELY(dir.exists())) {
160  const auto _pref = prefix.isEmpty() ? QStringLiteral(".") : prefix;
161  const auto _suff = suffix.isEmpty() ? QStringLiteral(".qm") : suffix;
162  const QString filter = name + _pref + u'*' + _suff;
163  const auto files = dir.entryInfoList({name}, QDir::Files);
164  if (Q_LIKELY(!files.empty())) {
165  d->locales.reserve(files.size());
166  bool shrinkToFit = false;
167  for (const QFileInfo &fi : files) {
168  const auto fn = fi.fileName();
169  const auto prefIdx = fn.indexOf(_pref);
170  const auto locPart =
171  fn.mid(prefIdx + _pref.length(),
172  fn.length() - prefIdx - _suff.length() - _pref.length());
173  QLocale l(locPart);
174  if (Q_LIKELY(l.language() != QLocale::C)) {
175  d->locales.push_back(l);
176  qCDebug(C_LANGSELECT,
177  "Added locale \"%s\" to the list of supported locales.",
178  qUtf8Printable(locPart));
179  } else {
180  shrinkToFit = true;
181  qCWarning(
182  C_LANGSELECT,
183  "Can not add invalid locale \"%s\" to the list of supported locales.",
184  qUtf8Printable(locPart));
185  }
186  }
187  if (shrinkToFit) {
188  d->locales.squeeze();
189  }
190  } else {
191  qCWarning(C_LANGSELECT,
192  "Can not find translation files for \"%s\" in \"%s\".",
193  qUtf8Printable(filter),
194  qUtf8Printable(path));
195  }
196  } else {
197  qCWarning(C_LANGSELECT,
198  "Can not set locales from not existing directory \"%s\".",
199  qUtf8Printable(path));
200  }
201  } else {
202  qCWarning(C_LANGSELECT, "Can not set locales from dir with empty path or name.");
203  }
204 }
205 
206 void LangSelect::setLocalesFromDirs(const QString &path, const QString &name)
207 {
208  Q_D(LangSelect);
209  d->locales.clear();
210  if (Q_LIKELY(!path.isEmpty() && !name.isEmpty())) {
211  const QDir dir(path);
212  if (Q_LIKELY(dir.exists())) {
213  const auto dirs = dir.entryList(QDir::AllDirs);
214  if (Q_LIKELY(!dirs.empty())) {
215  d->locales.reserve(dirs.size());
216  bool shrinkToFit = false;
217  for (const QString &subDir : dirs) {
218  const QString relFn = subDir + u'/' + name;
219  if (dir.exists(relFn)) {
220  QLocale l(subDir);
221  if (Q_LIKELY(l.language() != QLocale::C)) {
222  d->locales.push_back(l);
223  qCDebug(C_LANGSELECT,
224  "Added locale \"%s\" to the list of supported locales.",
225  qUtf8Printable(subDir));
226  } else {
227  shrinkToFit = true;
228  qCWarning(C_LANGSELECT,
229  "Can not add invalid locale \"%s\" to the list of supported "
230  "locales.",
231  qUtf8Printable(subDir));
232  }
233  } else {
234  shrinkToFit = true;
235  }
236  }
237  if (shrinkToFit) {
238  d->locales.squeeze();
239  }
240  }
241  } else {
242  qCWarning(C_LANGSELECT,
243  "Can not set locales from not existing directory \"%s\".",
244  qUtf8Printable(path));
245  }
246  } else {
247  qCWarning(C_LANGSELECT, "Can not set locales from dirs with empty path or names.");
248  }
249 }
250 
252 {
253  Q_D(const LangSelect);
254  return d->locales;
255 }
256 
258 {
259  Q_D(LangSelect);
260  d->queryKey = key;
261 }
262 
264 {
265  Q_D(LangSelect);
266  d->sessionKey = key;
267 }
268 
270 {
271  Q_D(LangSelect);
272  d->cookieName = name;
273 }
274 
276 {
277  Q_D(LangSelect);
278  d->subDomainMap.clear();
279  d->locales.clear();
280  d->locales.reserve(map.size());
281  auto i = map.constBegin();
282  while (i != map.constEnd()) {
283  if (i.value().language() != QLocale::C) {
284  d->subDomainMap.insert(i.key(), i.value());
285  d->locales.append(i.value());
286  } else {
287  qCWarning(C_LANGSELECT) << "Can not add invalid locale" << i.value() << "for subdomain"
288  << i.key() << "to the subdomain map.";
289  }
290  ++i;
291  }
292  d->locales.squeeze();
293 }
294 
296 {
297  Q_D(LangSelect);
298  d->domainMap.clear();
299  d->locales.clear();
300  d->locales.reserve(map.size());
301  auto i = map.constBegin();
302  while (i != map.constEnd()) {
303  if (Q_LIKELY(i.value().language() != QLocale::C)) {
304  d->domainMap.insert(i.key(), i.value());
305  d->locales.append(i.value());
306  } else {
307  qCWarning(C_LANGSELECT) << "Can not add invalid locale" << i.value() << "for domain"
308  << i.key() << "to the domain map.";
309  }
310  ++i;
311  }
312  d->locales.squeeze();
313 }
314 
316 {
317  Q_D(LangSelect);
318  d->fallbackLocale = fallback;
319 }
320 
322 {
323  Q_D(LangSelect);
324  d->detectFromHeader = enabled;
325 }
326 
328 {
329  Q_D(LangSelect);
330  if (Q_LIKELY(!key.isEmpty())) {
331  d->langStashKey = key;
332  } else {
333  qCWarning(C_LANGSELECT) << "Can not set an empty key name for the language code stash key. "
334  "Using current key name"
335  << d->langStashKey;
336  }
337 }
338 
340 {
341  Q_D(LangSelect);
342  if (Q_LIKELY(!key.isEmpty())) {
343  d->dirStashKey = key;
344  } else {
345  qCWarning(C_LANGSELECT) << "Can not set an empty key name for the language direction stash "
346  "key. Using current key name"
347  << d->dirStashKey;
348  }
349 }
350 
352 {
353  if (!lsp) {
354  qCCritical(C_LANGSELECT) << "LangSelect plugin not registered";
355  return QVector<QLocale>();
356  }
357 
358  return lsp->supportedLocales();
359 }
360 
362 {
363  if (!lsp) {
364  qCCritical(C_LANGSELECT) << "LangSelect plugin not registered";
365  return true;
366  }
367 
368  const auto d = lsp->d_ptr;
369  const auto _key = !key.isEmpty() ? key : d->queryKey;
370  if (!d->getFromQuery(c, _key)) {
371  if (!d->getFromHeader(c)) {
372  d->setFallback(c);
373  }
374  d->setToQuery(c, _key);
375  c->detach();
376  return false;
377  }
378  d->setContentLanguage(c);
379 
380  return true;
381 }
382 
384 {
385  bool foundInSession = false;
386 
387  if (!lsp) {
388  qCCritical(C_LANGSELECT) << "LangSelect plugin not registered";
389  return foundInSession;
390  }
391 
392  const auto d = lsp->d_ptr;
393  const auto _key = !key.isEmpty() ? key : d->sessionKey;
394  foundInSession = d->getFromSession(c, _key);
395  if (!foundInSession) {
396  if (!d->getFromHeader(c)) {
397  d->setFallback(c);
398  }
399  d->setToSession(c, _key);
400  }
401  d->setContentLanguage(c);
402 
403  return foundInSession;
404 }
405 
407 {
408  bool foundInCookie = false;
409 
410  if (!lsp) {
411  qCCritical(C_LANGSELECT) << "LangSelect plugin not registered";
412  return foundInCookie;
413  }
414 
415  const auto d = lsp->d_ptr;
416  const auto _name = !name.isEmpty() ? name : d->cookieName;
417  foundInCookie = d->getFromCookie(c, _name);
418  if (!foundInCookie) {
419  if (!d->getFromHeader(c)) {
420  d->setFallback(c);
421  }
422  d->setToCookie(c, _name);
423  }
424  d->setContentLanguage(c);
425 
426  return foundInCookie;
427 }
428 
430 {
431  bool foundInSubDomain = false;
432 
433  if (!lsp) {
434  qCCritical(C_LANGSELECT) << "LangSelect plugin not registered";
435  return foundInSubDomain;
436  }
437 
438  const auto d = lsp->d_ptr;
439  const auto _map = !subDomainMap.empty() ? subDomainMap : d->subDomainMap;
440  foundInSubDomain = d->getFromSubdomain(c, _map);
441  if (!foundInSubDomain) {
442  if (!d->getFromHeader(c)) {
443  d->setFallback(c);
444  }
445  }
446 
447  d->setContentLanguage(c);
448 
449  return foundInSubDomain;
450 }
451 
453 {
454  bool foundInDomain = false;
455 
456  if (!lsp) {
457  qCCritical(C_LANGSELECT) << "LangSelect plugin not registered";
458  return foundInDomain;
459  }
460 
461  const auto d = lsp->d_ptr;
462  const auto _map = !domainMap.empty() ? domainMap : d->domainMap;
463  foundInDomain = d->getFromDomain(c, _map);
464  if (!foundInDomain) {
465  if (!d->getFromHeader(c)) {
466  d->setFallback(c);
467  }
468  }
469 
470  d->setContentLanguage(c);
471 
472  return foundInDomain;
473 }
474 
475 bool LangSelect::fromPath(Context *c, const QString &locale)
476 {
477  if (!lsp) {
478  qCCritical(C_LANGSELECT) << "LangSelect plugin not registered";
479  return true;
480  }
481 
482  const auto d = lsp->d_ptr;
483  const QLocale l(locale);
484  if (l.language() != QLocale::C && d->locales.contains(l)) {
485  qCDebug(C_LANGSELECT) << "Found valid locale" << l << "in path";
486  c->setLocale(l);
487  d->setContentLanguage(c);
488  return true;
489  } else {
490  if (!d->getFromHeader(c)) {
491  d->setFallback(c);
492  }
493  auto uri = c->req()->uri();
494  auto pathParts = uri.path().split(u'/');
495  const auto localeIdx = pathParts.indexOf(locale);
496  pathParts[localeIdx] = c->locale().bcp47Name().toLower();
497  uri.setPath(pathParts.join(u'/'));
498  qCDebug(C_LANGSELECT) << "Storing selected locale by redirecting to" << uri;
499  c->res()->redirect(uri, 307);
500  c->detach();
501  return false;
502  }
503 }
504 
505 bool LangSelectPrivate::detectLocale(Context *c, LangSelect::Source _source, bool *skipMethod) const
506 {
507  bool redirect = false;
508 
510 
511  if (_source == LangSelect::Session) {
512  if (getFromSession(c, sessionKey)) {
513  foundIn = _source;
514  }
515  } else if (_source == LangSelect::Cookie) {
516  if (getFromCookie(c, cookieName)) {
517  foundIn = _source;
518  }
519  } else if (_source == LangSelect::URLQuery) {
520  if (getFromQuery(c, queryKey)) {
521  foundIn = _source;
522  }
523  } else if (_source == LangSelect::SubDomain) {
524  if (getFromSubdomain(c, subDomainMap)) {
525  foundIn = _source;
526  }
527  } else if (_source == LangSelect::Domain) {
528  if (getFromDomain(c, domainMap)) {
529  foundIn = _source;
530  }
531  }
532 
533  // could not find supported locale in specified source
534  // falling back to Accept-Language header
535  if (foundIn == LangSelect::Fallback && getFromHeader(c)) {
536  foundIn = LangSelect::AcceptHeader;
537  }
538 
539  if (foundIn == LangSelect::Fallback) {
540  setFallback(c);
541  }
542 
543  if (foundIn != _source) {
544  if (_source == LangSelect::Session) {
545  setToSession(c, sessionKey);
546  } else if (_source == LangSelect::Cookie) {
547  setToCookie(c, cookieName);
548  } else if (_source == LangSelect::URLQuery) {
549  setToQuery(c, queryKey);
550  redirect = true;
551  if (skipMethod) {
552  *skipMethod = true;
553  }
554  }
555  }
556 
557  if (!redirect) {
558  setContentLanguage(c);
559  }
560 
561  return redirect;
562 }
563 
564 bool LangSelectPrivate::getFromQuery(Context *c, const QString &key) const
565 {
566  const QLocale l(c->req()->queryParam(key));
567  if (l.language() != QLocale::C && locales.contains(l)) {
568  qCDebug(C_LANGSELECT) << "Found valid locale" << l << "in url query key" << key;
569  c->setLocale(l);
570  return true;
571  } else {
572  qCDebug(C_LANGSELECT) << "Can not find supported locale in url query key" << key;
573  return false;
574  }
575 }
576 
577 bool LangSelectPrivate::getFromCookie(Context *c, const QString &cookie) const
578 {
579  const QLocale l(c->req()->cookie(cookie));
580  if (l.language() != QLocale::C && locales.contains(l)) {
581  qCDebug(C_LANGSELECT) << "Found valid locale" << l << "in cookie name" << cookie;
582  c->setLocale(l);
583  return true;
584  } else {
585  qCDebug(C_LANGSELECT) << "Can no find supported locale in cookie value with name" << cookie;
586  return false;
587  }
588 }
589 
590 bool LangSelectPrivate::getFromSession(Context *c, const QString &key) const
591 {
592  const QLocale l = Cutelyst::Session::value(c, key).toLocale();
593  if (l.language() != QLocale::C && locales.contains(l)) {
594  qCDebug(C_LANGSELECT) << "Found valid locale" << l << "in session key" << key;
595  c->setLocale(l);
596  return true;
597  } else {
598  qCDebug(C_LANGSELECT) << "Can not find supported locale in session value with key" << key;
599  return false;
600  }
601 }
602 
603 bool LangSelectPrivate::getFromSubdomain(Context *c, const QMap<QString, QLocale> &map) const
604 {
605  const auto domain = c->req()->uri().host();
606  auto i = map.constBegin();
607  while (i != map.constEnd()) {
608  if (domain.startsWith(i.key())) {
609  qCDebug(C_LANGSELECT) << "Found valid locale" << i.value()
610  << "in subdomain map for domain" << domain;
611  c->setLocale(i.value());
612  return true;
613  }
614  ++i;
615  }
616 
617  const auto domainParts = domain.split(u'.', Qt::SkipEmptyParts);
618  if (domainParts.size() > 2) {
619  const QLocale l(domainParts.at(0));
620  if (l.language() != QLocale::C && locales.contains(l)) {
621  qCDebug(C_LANGSELECT) << "Found supported locale" << l << "in subdomain of domain"
622  << domain;
623  c->setLocale(l);
624  return true;
625  }
626  }
627  qCDebug(C_LANGSELECT) << "Can not find supported locale for subdomain" << domain;
628  return false;
629 }
630 
631 bool LangSelectPrivate::getFromDomain(Context *c, const QMap<QString, QLocale> &map) const
632 {
633  const auto domain = c->req()->uri().host();
634  auto i = map.constBegin();
635  while (i != map.constEnd()) {
636  if (domain.endsWith(i.key())) {
637  qCDebug(C_LANGSELECT) << "Found valid locale" << i.value() << "in domain map for domain"
638  << domain;
639  c->setLocale(i.value());
640  return true;
641  }
642  ++i;
643  }
644 
645  const auto domainParts = domain.split(u'.', Qt::SkipEmptyParts);
646  if (domainParts.size() > 1) {
647  const QLocale l(domainParts.at(domainParts.size() - 1));
648  if (l.language() != QLocale::C && locales.contains(l)) {
649  qCDebug(C_LANGSELECT) << "Found supported locale" << l << "in domain" << domain;
650  c->setLocale(l);
651  return true;
652  }
653  }
654  qCDebug(C_LANGSELECT) << "Can not find supported locale for domain" << domain;
655  return false;
656 }
657 
658 bool LangSelectPrivate::getFromHeader(Context *c, const QString &name) const
659 {
660  if (detectFromHeader) {
661  const auto accpetedLangs = c->req()->header(name).split(u',', Qt::SkipEmptyParts);
662  if (Q_LIKELY(!accpetedLangs.empty())) {
663  std::map<float, QLocale> langMap;
664  for (const QString &al : accpetedLangs) {
665  const auto idx = al.indexOf(u';');
666  float priority = 1.0f;
667  QString langPart;
668  bool ok = true;
669  if (idx > -1) {
670  langPart = al.left(idx);
671  const auto ref = QStringView(al).mid(idx + 1);
672  priority = ref.mid(ref.indexOf(u'=') + 1).toFloat(&ok);
673  } else {
674  langPart = al;
675  }
676  QLocale locale(langPart);
677  if (ok && locale.language() != QLocale::C) {
678  const auto search = langMap.find(priority);
679  if (search == langMap.cend()) {
680  langMap.insert({priority, locale});
681  }
682  }
683  }
684  if (!langMap.empty()) {
685  auto i = langMap.crbegin();
686  while (i != langMap.crend()) {
687  if (locales.contains(i->second)) {
688  c->setLocale(i->second);
689  qCDebug(C_LANGSELECT)
690  << "Selected locale" << c->locale() << "from" << name << "header";
691  return true;
692  }
693  ++i;
694  }
695  // if there is no exact match, lets try to find a locale
696  // where at least the language matches
697  i = langMap.crbegin();
698  const auto constLocales = locales;
699  while (i != langMap.crend()) {
700  for (const QLocale &l : constLocales) {
701  if (l.language() == i->second.language()) {
702  c->setLocale(l);
703  qCDebug(C_LANGSELECT)
704  << "Selected locale" << c->locale() << "from" << name << "header";
705  return true;
706  }
707  }
708  ++i;
709  }
710  }
711  }
712  }
713 
714  return false;
715 }
716 
717 void LangSelectPrivate::setToQuery(Context *c, const QString &key) const
718 {
719  auto uri = c->req()->uri();
720  QUrlQuery query(uri);
721  if (query.hasQueryItem(key)) {
722  query.removeQueryItem(key);
723  }
724  query.addQueryItem(key, c->locale().bcp47Name().toLower());
725  uri.setQuery(query);
726  qCDebug(C_LANGSELECT) << "Storing selected locale in URL query by redirecting to" << uri;
727  c->res()->redirect(uri, 307);
728 }
729 
730 void LangSelectPrivate::setToCookie(Context *c, const QString &name) const
731 {
732  qCDebug(C_LANGSELECT) << "Storing selected locale in cookie with name" << name;
733  QNetworkCookie cookie(name.toLatin1(), c->locale().bcp47Name().toLatin1());
734 #if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
735  cookie.setSameSitePolicy(QNetworkCookie::SameSite::Lax);
736 #endif
737  c->res()->setCookie(cookie);
738 }
739 
740 void LangSelectPrivate::setToSession(Context *c, const QString &key) const
741 {
742  qCDebug(C_LANGSELECT) << "Storing selected locale in session key" << key;
743  Session::setValue(c, key, c->locale());
744 }
745 
746 void LangSelectPrivate::setFallback(Context *c) const
747 {
748  qCDebug(C_LANGSELECT) << "Can not find fitting locale, using fallback locale" << fallbackLocale;
749  c->setLocale(fallbackLocale);
750 }
751 
752 void LangSelectPrivate::setContentLanguage(Context *c) const
753 {
754  if (addContentLanguageHeader) {
755  c->res()->setHeader(QStringLiteral("Content-Language"), c->locale().bcp47Name());
756  }
757  c->stash({{langStashKey, c->locale().bcp47Name()},
758  {dirStashKey,
759  (c->locale().textDirection() == Qt::LeftToRight ? QStringLiteral("ltr")
760  : QStringLiteral("rtl"))}});
761 }
762 
763 void LangSelectPrivate::beforePrepareAction(Context *c, bool *skipMethod) const
764 {
765  if (*skipMethod) {
766  return;
767  }
768 
769  if (!c->stash(SELECTION_TRIED).isNull()) {
770  return;
771  }
772 
773  detectLocale(c, source, skipMethod);
774 
775  c->setStash(SELECTION_TRIED, true);
776 }
777 
778 void LangSelectPrivate::_q_postFork(Application *app)
779 {
780  lsp = app->plugin<LangSelect *>();
781 }
782 
783 #include "moc_langselect.cpp"
The Cutelyst Application.
Definition: application.h:43
void beforePrepareAction(Cutelyst::Context *c, bool *skipMethod)
T plugin()
Returns the registered plugin that casts to the template type T.
Definition: application.h:107
void postForked(Cutelyst::Application *app)
The Cutelyst Context.
Definition: context.h:39
void stash(const QVariantHash &unite)
Definition: context.cpp:560
void detach(Action *action=nullptr)
Definition: context.cpp:345
QLocale locale() const noexcept
Definition: context.cpp:460
Response * res() const noexcept
Definition: context.cpp:102
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:217
void setLocale(const QLocale &locale)
Definition: context.cpp:466
The Cutelyst Cookie.
Definition: cookie.h:29
Language selection plugin.
Definition: langselect.h:330
void setLocalesFromDir(const QString &path, const QString &name, const QString &prefix=QStringLiteral("."), const QString &suffix=QStringLiteral(".qm"))
Definition: langselect.cpp:150
void setDetectFromHeader(bool enabled)
Definition: langselect.cpp:321
void setLanguageDirStashKey(const QString &key=QStringLiteral("c_langselect_dir"))
Definition: langselect.cpp:339
static bool fromPath(Context *c, const QString &locale)
Definition: langselect.cpp:475
static QVector< QLocale > getSupportedLocales()
Definition: langselect.cpp:351
void setFallbackLocale(const QLocale &fallback)
Definition: langselect.cpp:315
void setQueryKey(const QString &key)
Definition: langselect.cpp:257
void setSubDomainMap(const QMap< QString, QLocale > &map)
Definition: langselect.cpp:275
static bool fromCookie(Context *c, const QString &name=QString())
Definition: langselect.cpp:406
static bool fromDomain(Context *c, const QMap< QString, QLocale > &domainMap=QMap< QString, QLocale >())
Definition: langselect.cpp:452
void setDomainMap(const QMap< QString, QLocale > &map)
Definition: langselect.cpp:295
void setLanguageCodeStashKey(const QString &key=QStringLiteral("c_langselect_lang"))
Definition: langselect.cpp:327
static bool fromUrlQuery(Context *c, const QString &key=QString())
Definition: langselect.cpp:361
void setCookieName(const QString &name)
Definition: langselect.cpp:269
static bool fromSession(Context *c, const QString &key=QString())
Definition: langselect.cpp:383
void setLocalesFromDirs(const QString &path, const QString &name)
Definition: langselect.cpp:206
static bool fromSubDomain(Context *c, const QMap< QString, QLocale > &subDomainMap=QMap< QString, QLocale >())
Definition: langselect.cpp:429
QVector< QLocale > supportedLocales() const
Definition: langselect.cpp:251
virtual ~LangSelect() override
Definition: langselect.cpp:48
void setSessionKey(const QString &key)
Definition: langselect.cpp:263
void addSupportedLocale(const QLocale &locale)
Definition: langselect.cpp:126
virtual bool setup(Application *app) override
Definition: langselect.cpp:53
void setSupportedLocales(const QVector< QLocale > &locales)
Definition: langselect.cpp:94
LangSelect(Application *parent, Source source)
Definition: langselect.cpp:30
QString header(const QString &key) const
Definition: request.h:581
QString queryParam(const QString &key, const QString &defaultValue={}) const
Definition: request.h:561
QString cookie(const QString &name) const
Definition: request.cpp:274
void redirect(const QUrl &url, quint16 status=Found)
Definition: response.cpp:278
void setHeader(const QString &field, const QString &value)
void setCookie(const QNetworkCookie &cookie)
Definition: response.cpp:232
static QVariant value(Context *c, const QString &key, const QVariant &defaultValue=QVariant())
Definition: session.cpp:170
static void setValue(Context *c, const QString &key, const QVariant &value)
Definition: session.cpp:185
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:8
QFileInfoList entryInfoList(QDir::Filters filters, QDir::SortFlags sort) const const
QStringList entryList(QDir::Filters filters, QDir::SortFlags sort) const const
bool exists() const const
void reserve(int alloc)
int size() 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
bool empty() const const
int size() 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
float toFloat(bool *ok) const const
LeftToRight
SkipEmptyParts
QString host(QUrl::ComponentFormattingOptions options) const const
QString path(QUrl::ComponentFormattingOptions options) const const
QLocale toLocale() const const
int size() const const