Cutelyst  2.5.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 
107 void LangSelect::setSupportedLocales(const QVector<QLocale> &locales)
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 
121 void LangSelect::setSupportedLocales(const QStringList &locales)
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 
136 void LangSelect::addSupportedLocale(const QLocale &locale)
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 
146 void LangSelect::addSupportedLocale(const QString &locale)
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 
236 QVector<QLocale> LangSelect::supportedLocales() const
237 {
238  Q_D(const LangSelect);
239  return d->locales;
240 }
241 
242 void LangSelect::setQueryKey(const QString &key)
243 {
244  Q_D(LangSelect);
245  d->queryKey = key;
246 }
247 
248 void LangSelect::setSessionKey(const QString &key)
249 {
250  Q_D(LangSelect);
251  d->sessionKey = key;
252 }
253 
254 void LangSelect::setCookieName(const QString &name)
255 {
256  Q_D(LangSelect);
257  d->cookieName = name;
258 }
259 
260 void LangSelect::setSubDomainMap(const QMap<QString, QLocale> &map)
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 
279 void LangSelect::setDomainMap(const QMap<QString, QLocale> &map)
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 
298 void LangSelect::setFallbackLocale(const QLocale &fallback)
299 {
300  Q_D(LangSelect);
301  d->fallbackLocale = fallback;
302 }
303 
305 {
306  Q_D(LangSelect);
307  d->detectFromHeader = enabled;
308 }
309 
310 void LangSelect::setLanguageCodeStashKey(const QString &key)
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 
320 void LangSelect::setLanguageDirStashKey(const QString &key)
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 
340 bool LangSelect::fromUrlQuery(Context *c, const QString &key)
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 
362 bool LangSelect::fromSession(Context *c, const QString &key)
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 
385 bool LangSelect::fromCookie(Context *c, const QString &name)
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 
408 bool LangSelect::fromSubDomain(Context *c, const QMap<QString, QLocale> &subDomainMap)
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 
431 bool LangSelect::fromDomain(Context *c, const QMap<QString,QLocale> &domainMap)
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  const auto domainParts = domain.split(QLatin1Char('.'), QString::SkipEmptyParts);
596  if (domainParts.size() > 2) {
597  const QLocale l(domainParts.at(0));
598  if (l.language() != QLocale::C && locales.contains(l)) {
599  qCDebug(C_LANGSELECT) << "Found supported locale" << l << "in subdomain of domain" << domain;
600  c->setLocale(l);
601  return true;
602  }
603  }
604  qCDebug(C_LANGSELECT) << "Can not find supported locale for subdomain" << domain;
605  return false;
606 }
607 
608 bool LangSelectPrivate::getFromDomain(Context *c, const QMap<QString, QLocale> &map) const
609 {
610  const auto domain = c->req()->uri().host();
611  auto i = map.constBegin();
612  while (i != map.constEnd()) {
613  if (domain.endsWith(i.key())) {
614  qCDebug(C_LANGSELECT) << "Found valid locale" << i.value() << "in domain map for domain" << domain;
615  c->setLocale(i.value());
616  return true;
617  }
618  ++i;
619  }
620  const auto domainParts = domain.split(QLatin1Char('.'), QString::SkipEmptyParts);
621  if (domainParts.size() > 1) {
622  const QLocale l(domainParts.at(domainParts.size() - 1));
623  if (l.language() != QLocale::C && locales.contains(l)) {
624  qCDebug(C_LANGSELECT) << "Found supported locale" << l << "in domain" << domain;
625  c->setLocale(l);
626  return true;
627  }
628  }
629  qCDebug(C_LANGSELECT) << "Can not find supported locale for domain" << domain;
630  return false;
631 }
632 
633 bool LangSelectPrivate::getFromHeader(Context *c, const QString &name) const
634 {
635  if (detectFromHeader) {
636  const auto accpetedLangs = c->req()->header(name).split(QLatin1Char(','), QString::SkipEmptyParts);
637  if (Q_LIKELY(!accpetedLangs.empty())) {
638  std::map<float,QLocale> langMap;
639  for (const QString &al : accpetedLangs) {
640  const auto idx = al.indexOf(QLatin1Char(';'));
641  float priority = 1.0f;
642  QString langPart;
643  bool ok = true;
644  if (idx > -1) {
645  langPart = al.left(idx);
646  const QStringRef ref = al.midRef(idx + 1);
647  priority = ref.mid(ref.indexOf(QLatin1Char('=')) +1).toFloat(&ok);
648  } else {
649  langPart = al;
650  }
651  QLocale locale(langPart);
652  if (ok && locale.language() != QLocale::C) {
653  const auto search = langMap.find(priority);
654  if (search == langMap.cend()) {
655  langMap.insert({priority, locale});
656  }
657  }
658  }
659  if (!langMap.empty()) {
660  auto i = langMap.crbegin();
661  while (i != langMap.crend()) {
662  if (locales.contains(i->second)) {
663  c->setLocale(i->second);
664  qCDebug(C_LANGSELECT) << "Selected locale" << c->locale() << "from" << name << "header";
665  return true;
666  }
667  ++i;
668  }
669  // if there is no exact match, lets try to find a locale
670  // where at least the language matches
671  i = langMap.crbegin();
672  const auto constLocales = locales;
673  while (i != langMap.crend()) {
674  for (const QLocale &l : constLocales) {
675  if (l.language() == i->second.language()) {
676  c->setLocale(l);
677  qCDebug(C_LANGSELECT) << "Selected locale" << c->locale() << "from" << name << "header";
678  return true;
679  }
680  }
681  ++i;
682  }
683  }
684  }
685  }
686 
687  return false;
688 }
689 
690 void LangSelectPrivate::setToQuery(Context *c, const QString &key) const
691 {
692  auto uri = c->req()->uri();
693  QUrlQuery query(uri);
694  if (query.hasQueryItem(key)) {
695  query.removeQueryItem(key);
696  }
697  query.addQueryItem(key, c->locale().bcp47Name().toLower());
698  uri.setQuery(query);
699  qCDebug(C_LANGSELECT) << "Storing selected locale in URL query by redirecting to" << uri;
700  c->res()->redirect(uri, 307);
701 }
702 
703 void LangSelectPrivate::setToCookie(Context *c, const QString &name) const
704 {
705  qCDebug(C_LANGSELECT) << "Storing selected locale in cookie with name" << name;
706  c->res()->setCookie(QNetworkCookie(name.toLatin1(), c->locale().bcp47Name().toLatin1()));
707 }
708 
709 void LangSelectPrivate::setToSession(Context *c, const QString &key) const
710 {
711  qCDebug(C_LANGSELECT) << "Storing selected locale in session key" << key;
712  Session::setValue(c, key, c->locale());
713 }
714 
715 void LangSelectPrivate::setFallback(Context *c) const
716 {
717  qCDebug(C_LANGSELECT) << "Can not find fitting locale, using fallback locale" << fallbackLocale;
718  c->setLocale(fallbackLocale);
719 }
720 
721 void LangSelectPrivate::setContentLanguage(Context *c) const
722 {
723  if (addContentLanguageHeader) {
724  c->res()->setHeader(QStringLiteral("Content-Language"), c->locale().bcp47Name());
725  }
726  c->stash({
727  {langStashKey, c->locale().bcp47Name()},
728  {dirStashKey, (c->locale().textDirection() == Qt::LeftToRight ? QStringLiteral("ltr") : QStringLiteral("rtl"))}
729  });
730 }
731 
732 void LangSelectPrivate::beforePrepareAction(Context *c, bool *skipMethod) const
733 {
734  if (*skipMethod) {
735  return;
736  }
737 
738  if (!c->stash(SELECTION_TRIED).isNull()) {
739  return;
740  }
741 
742  detectLocale(c, source, skipMethod);
743 
744  c->setStash(SELECTION_TRIED, true);
745 }
746 
747 void LangSelectPrivate::_q_postFork(Application *app)
748 {
749  lsp = app->plugin<LangSelect *>();
750 }
751 
752 #include "moc_langselect.cpp"
void setCookie(const QNetworkCookie &cookie)
Definition: response.cpp:211
void setHeader(const QString &field, const QString &value)
Definition: response.cpp:280
static bool fromPath(Context *c, const QString &locale)
Definition: langselect.cpp:454
Response * res() const
Definition: context.cpp:116
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:207
void detach(Action *action=nullptr)
Definition: context.cpp:317
T plugin()
Returns the registered plugin that casts to the template type T.
Definition: application.h:115
void setQueryKey(const QString &key)
Definition: langselect.cpp:242
QVector< QLocale > supportedLocales() const
Definition: langselect.cpp:236
void setSupportedLocales(const QVector< QLocale > &locales)
Definition: langselect.cpp:107
QLocale locale() const
Definition: context.cpp:390
The Cutelyst Context.
Definition: context.h:50
static bool fromCookie(Context *c, const QString &name=QString())
Definition: langselect.cpp:385
void setLanguageCodeStashKey(const QString &key=QStringLiteral("c_langselect_lang"))
Definition: langselect.cpp:310
void redirect(const QUrl &url, quint16 status=Found)
Definition: response.cpp:231
static bool fromUrlQuery(Context *c, const QString &key=QString())
Definition: langselect.cpp:340
void setLocalesFromDirs(const QString &path, const QString &name)
Definition: langselect.cpp:198
QString header(const QString &key) const
Definition: request.h:557
static void setValue(Context *c, const QString &key, const QVariant &value)
Definition: session.cpp:176
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
void beforePrepareAction(Context *c, bool *skipMethod)
void setLanguageDirStashKey(const QString &key=QStringLiteral("c_langselect_dir"))
Definition: langselect.cpp:320
void addSupportedLocale(const QLocale &locale)
Definition: langselect.cpp:136
void postForked(Application *app)
void setLocale(const QLocale &locale)
Definition: context.cpp:396
static bool fromDomain(Context *c, const QMap< QString, QLocale > &domainMap=QMap< QString, QLocale >())
Definition: langselect.cpp:431
Language selection plugin.
Definition: langselect.h:312
virtual bool setup(Application *app) override
Definition: langselect.cpp:66
static QVector< QLocale > getSupportedLocales()
Definition: langselect.cpp:330
void setCookieName(const QString &name)
Definition: langselect.cpp:254
void setSubDomainMap(const QMap< QString, QLocale > &map)
Definition: langselect.cpp:260
void setDomainMap(const QMap< QString, QLocale > &map)
Definition: langselect.cpp:279
void setSessionKey(const QString &key)
Definition: langselect.cpp:248
LangSelect(Application *parent, Source source)
Definition: langselect.cpp:44
static QVariant value(Context *c, const QString &key, const QVariant &defaultValue=QVariant())
Definition: session.cpp:161
QString cookie(const QString &name) const
Definition: request.cpp:284
The Cutelyst Application.
Definition: application.h:55
static bool fromSession(Context *c, const QString &key=QString())
Definition: langselect.cpp:362
void setFallbackLocale(const QLocale &fallback)
Definition: langselect.cpp:298
static bool fromSubDomain(Context *c, const QMap< QString, QLocale > &subDomainMap=QMap< QString, QLocale >())
Definition: langselect.cpp:408
void setDetectFromHeader(bool enabled)
Definition: langselect.cpp:304
void stash(const QVariantHash &unite)
Definition: context.h:515
void setLocalesFromDir(const QString &path, const QString &name, const QString &prefix=QStringLiteral("."), const QString &suffix=QStringLiteral(".qm"))
Definition: langselect.cpp:157
QString queryParam(const QString &key, const QString &defaultValue=QString()) const
Definition: request.h:545