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