cutelyst  3.9.1
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"
void setCookie(const QNetworkCookie &cookie)
Definition: response.cpp:232
void postForked(Cutelyst::Application *app)
bool empty() const const
void setHeader(const QString &field, const QString &value)
static bool fromPath(Context *c, const QString &locale)
Definition: langselect.cpp:475
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void reserve(qsizetype size)
Response * res() const noexcept
Definition: context.cpp:102
const_iterator constBegin() const const
QStringView mid(qsizetype start, qsizetype length) const const
QString host(ComponentFormattingOptions options) const const
virtual ~LangSelect() override
Definition: langselect.cpp:48
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:217
void detach(Action *action=nullptr)
Definition: context.cpp:345
T plugin()
Returns the registered plugin that casts to the template type T.
Definition: application.h:107
QString queryParam(const QString &key, const QString &defaultValue={}) const
Definition: request.h:561
LeftToRight
void setQueryKey(const QString &key)
Definition: langselect.cpp:257
QVector< QLocale > supportedLocales() const
Definition: langselect.cpp:251
qsizetype size() const const
void setSupportedLocales(const QVector< QLocale > &locales)
Definition: langselect.cpp:94
The Cutelyst Context.
Definition: context.h:38
static bool fromCookie(Context *c, const QString &name=QString())
Definition: langselect.cpp:406
void setLanguageCodeStashKey(const QString &key=QStringLiteral("c_langselect_lang"))
Definition: langselect.cpp:327
bool exists() const const
void redirect(const QUrl &url, quint16 status=Found)
Definition: response.cpp:278
static bool fromUrlQuery(Context *c, const QString &key=QString())
Definition: langselect.cpp:361
void stash(const QVariantHash &unite)
Definition: context.cpp:566
void beforePrepareAction(Cutelyst::Context *c, bool *skipMethod)
QFileInfoList entryInfoList(Filters filters, SortFlags sort) const const
bool isEmpty() const const
void setLocalesFromDirs(const QString &path, const QString &name)
Definition: langselect.cpp:206
const_iterator constEnd() const const
Qt::LayoutDirection textDirection() const const
QString header(const QString &key) const
Definition: request.h:581
QString path(ComponentFormattingOptions options) const const
Language language() const const
const T & value() const const
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:7
QLocale locale() const noexcept
Definition: context.cpp:466
qsizetype indexOf(const QRegularExpression &re, qsizetype from) const const
void setLanguageDirStashKey(const QString &key=QStringLiteral("c_langselect_dir"))
Definition: langselect.cpp:339
QString toLower() const const
SkipEmptyParts
void addSupportedLocale(const QLocale &locale)
Definition: langselect.cpp:126
void setLocale(const QLocale &locale)
Definition: context.cpp:472
static bool fromDomain(Context *c, const QMap< QString, QLocale > &domainMap=QMap< QString, QLocale >())
Definition: langselect.cpp:452
Language selection plugin.
Definition: langselect.h:329
QByteArray toLatin1() const const
virtual bool setup(Application *app) override
Definition: langselect.cpp:53
static QVector< QLocale > getSupportedLocales()
Definition: langselect.cpp:351
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
void setCookieName(const QString &name)
Definition: langselect.cpp:269
QStringList entryList(Filters filters, SortFlags sort) const const
void setSubDomainMap(const QMap< QString, QLocale > &map)
Definition: langselect.cpp:275
void setDomainMap(const QMap< QString, QLocale > &map)
Definition: langselect.cpp:295
QString bcp47Name() const const
void setSessionKey(const QString &key)
Definition: langselect.cpp:263
LangSelect(Application *parent, Source source)
Definition: langselect.cpp:30
static QVariant value(Context *c, const QString &key, const QVariant &defaultValue=QVariant())
Definition: session.cpp:170
QString left(qsizetype n) const const
QLocale toLocale() const const
QString cookie(const QString &name) const
Definition: request.cpp:274
The Cutelyst Application.
Definition: application.h:42
static bool fromSession(Context *c, const QString &key=QString())
Definition: langselect.cpp:383
void setFallbackLocale(const QLocale &fallback)
Definition: langselect.cpp:315
static bool fromSubDomain(Context *c, const QMap< QString, QLocale > &subDomainMap=QMap< QString, QLocale >())
Definition: langselect.cpp:429
The Cutelyst Cookie.
Definition: cookie.h:28
size_type size() const const
void setDetectFromHeader(bool enabled)
Definition: langselect.cpp:321
void setLocalesFromDir(const QString &path, const QString &name, const QString &prefix=QStringLiteral("."), const QString &suffix=QStringLiteral(".qm"))
Definition: langselect.cpp:150