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