cutelyst  4.4.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
Cutelyst::LangSelect Class Reference

Detect and select locale based on different input parameters. More...

#include <Cutelyst/Plugins/Utils/LangSelect>

Inheritance diagram for Cutelyst::LangSelect:

Protected Member Functions

bool setup (Application *app) override
 
- Protected Member Functions inherited from QObject
virtual void childEvent (QChildEvent *event)
 
virtual void connectNotify (const QMetaMethod &signal)
 
virtual void customEvent (QEvent *event)
 
virtual void disconnectNotify (const QMetaMethod &signal)
 
bool isSignalConnected (const QMetaMethod &signal) const const
 
int receivers (const char *signal) const const
 
QObjectsender () const const
 
int senderSignalIndex () const const
 
virtual void timerEvent (QTimerEvent *event)
 

Additional Inherited Members

- Public Member Functions inherited from Cutelyst::Plugin
 Plugin (Application *parent)
 
- Public Member Functions inherited from QObject
 QObject (QObject *parent)
 
QBindable< QStringbindableObjectName ()
 
bool blockSignals (bool block)
 
const QObjectListchildren () const const
 
QMetaObject::Connection connect (const QObject *sender, const char *signal, const char *method, Qt::ConnectionType type) const const
 
void deleteLater ()
 
void destroyed (QObject *obj)
 
bool disconnect (const char *signal, const QObject *receiver, const char *method) const const
 
bool disconnect (const QObject *receiver, const char *method) const const
 
void dumpObjectInfo () const const
 
void dumpObjectTree () const const
 
QList< QByteArraydynamicPropertyNames () const const
 
virtual bool event (QEvent *e)
 
virtual bool eventFilter (QObject *watched, QEvent *event)
 
findChild (const QString &name, Qt::FindChildOptions options) const const
 
QList< T > findChildren (const QRegularExpression &re, Qt::FindChildOptions options) const const
 
QList< T > findChildren (const QString &name, Qt::FindChildOptions options) const const
 
QList< T > findChildren (Qt::FindChildOptions options) const const
 
bool inherits (const char *className) const const
 
void installEventFilter (QObject *filterObj)
 
bool isQuickItemType () const const
 
bool isWidgetType () const const
 
bool isWindowType () const const
 
void killTimer (int id)
 
virtual const QMetaObjectmetaObject () const const
 
void moveToThread (QThread *targetThread)
 
QString objectName () const const
 
void objectNameChanged (const QString &objectName)
 
QObjectparent () const const
 
QVariant property (const char *name) const const
 
 Q_CLASSINFO (Name, Value)
 
 Q_EMIT Q_EMIT
 
 Q_ENUM (...)
 
 Q_ENUM_NS (...)
 
 Q_ENUMS (...)
 
 Q_FLAG (...)
 
 Q_FLAG_NS (...)
 
 Q_FLAGS (...)
 
 Q_GADGET Q_GADGET
 
 Q_GADGET_EXPORT (EXPORT_MACRO)
 
 Q_INTERFACES (...)
 
 Q_INVOKABLE Q_INVOKABLE
 
 Q_MOC_INCLUDE Q_MOC_INCLUDE
 
 Q_NAMESPACE Q_NAMESPACE
 
 Q_NAMESPACE_EXPORT (EXPORT_MACRO)
 
 Q_OBJECT Q_OBJECT
 
 Q_PROPERTY (...)
 
 Q_REVISION Q_REVISION
 
 Q_SET_OBJECT_NAME (Object)
 
 Q_SIGNAL Q_SIGNAL
 
 Q_SIGNALS Q_SIGNALS
 
 Q_SLOT Q_SLOT
 
 Q_SLOTS Q_SLOTS
 
qobject_cast (const QObject *object)
 
qobject_cast (QObject *object)
 
 QT_NO_NARROWING_CONVERSIONS_IN_CONNECT QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
 
void removeEventFilter (QObject *obj)
 
void setObjectName (const QString &name)
 
void setObjectName (QAnyStringView name)
 
void setParent (QObject *parent)
 
bool setProperty (const char *name, const QVariant &value)
 
bool setProperty (const char *name, QVariant &&value)
 
bool signalsBlocked () const const
 
int startTimer (int interval, Qt::TimerType timerType)
 
int startTimer (std::chrono::milliseconds interval, Qt::TimerType timerType)
 
QThreadthread () const const
 
- Static Public Member Functions inherited from QObject
QMetaObject::Connection connect (const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
 
QMetaObject::Connection connect (const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method, Qt::ConnectionType type)
 
QMetaObject::Connection connect (const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type)
 
QMetaObject::Connection connect (const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type)
 
QMetaObject::Connection connect (const QObject *sender, PointerToMemberFunction signal, Functor functor)
 
bool disconnect (const QMetaObject::Connection &connection)
 
bool disconnect (const QObject *sender, const char *signal, const QObject *receiver, const char *method)
 
bool disconnect (const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method)
 
bool disconnect (const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method)
 
QString tr (const char *sourceText, const char *disambiguation, int n)
 
- Public Attributes inherited from QObject
typedef QObjectList
 
- Properties inherited from QObject
 objectName
 

Detailed Description

The LangSelect plugin can be used to automatically detect and set a user’s language by querying various sources like session keys, cookies, path and subdomain components or the Accept-Language header sent by the user agent (like the web browser). It will compare the detected locale against a list of locales supported by the application to choose the most appropriate locale fitting the user’s preferences. If the language is not supported, it will use a fallback locale. As another fallback it will try to get the locale from the Accept-Language header.

Unless the plugin has been constructed with the manual mode constructor, it will be connected to the Application::beforePrepareAction() signal to set the locale. If auto detection is disabled, you can manually set the locale by calling LangSelect::fromCookie(), LangSelect::fromDomain(), LangSelect::fromPath(), LangSelect::fromSession(), LangSelect::fromUrlQuery() or LangSelect::fromSubDomain() at appropriate places.

Note that you must register plugins like StaticSimple before the LangSelect plugin, especially if you want to store the selected locale in the domain or the path.

On a multilingual site you will mostly have some kind of selector that allows users to choose the display language. Especially on publicly available content you might want to put the locale information into the domain or URL path to optimize your content for search engines.

The plugin will also set two values to the stash that will contain the BCP47 name of the selected locale and the text direction. You can set the stash keys used for this information with setLanguageCodeStashKey() and setLanguageDirStashKey().

Setting supported locales

One of the main purposes of this plugin is not only to select a locale, but also to select a locale that is supported by your application. Therefore the plugin provides different methods to set the list of supported locales. If you already use Application::loadTranslationsFromDir() or Application::loadTranslationsFromDirs() to load the translation files for your application, you can simply use the returned list of that methods and give them to setSupportedLocales(). If you use a different way of loading translations, have a look at the other functions to set the supported locales: addSupportedLocale(), setSupportedLocales(), setLocalesFromDir() and setLocalesFromDirs().

Modes of operation

The plugin can either work automatically or manually. The auto detection mode hooks into the Application::beforePrepareAction() signal to set the locale. The mode of operation is defined when constructing and registering the plugin. If auto detection is disabled, you can use one of the static functions that get and set the locale. Note that you still have to set the list of supported locales and might want to set some defaults for the sources like the session key, etc.

bool MyApp::init()
{
// register plugins like StaticSimple before the LangSelect
// plugin if you use the auto detection mode
auto stat = new StaticSimple(this);
// initializing the plugin in autodetection mode
// and using the session as source and storage
auto lsp = new LangSelect(this, LangSelect::Session);
lsp->setSessionKey(QStringLiteral("lang"));
}
bool MyApp::init()
{
// initializing the plugin in manual mode
// and setting a default value for the cookie name
auto lsp = new LangSelect(this);
lsp->setCookieName(QStringLiteral("lang"));
}

Sources

The LangSelect plugin supports different sources to get locale information from. Some sources only set the detected locale internally, other sources that rely on the request URI will perform a redirect to set the detected source. Common to all sources is, that they will fall back to the Accept-Language header and the locale set by setFallbackLocale() if no supported locale can be detected in the source. You can omit the Accept-Language header and use the fallback language directly by setting setDetectFromHeader() to false. If you set the source to LangSelect::AcceptHeader, the locale will always be extracted from the Accept-Langauge header filed from the request and will never be stored.

For the following examples we will assume that your application supports English, German and Portuguese and English is the fallback locale.

URL query

If you use LangSelect::URLQuery to register the plugin in auto mode or if you use LangSelect::fromUrlQuery() to get and set the locale from the url query manually at appropriate places in you application, the plugin will try to detect the locale from the query key specified for the plugin.

bool MyApp::init()
{
// registering the plugin in auto mode using
// url query as source and setting "lang" as
// query key
auto lsp = new LangSelect(this, LangSelect::URLQuery);
lsp->setQueryKey(QStringLiteral("lang"));
lsp->setSupportedLocales({
});
lsp->setFallbackLocale(QLocale(QLocale::English));
}

If a user now requests a resource by the URL https://www.example.com/my/path?lang=pt the plugin will automatically select Portuguese as locale and will set it to Context::setLocale(). If the use would call the URL https://www.example.com/my/path?lang=ru the plugin would redirect him to a locale matching the Accept-Language header of the browser. If that would contain some form of German or Portuguese, the redirection would be performed to the language that would have the highest priority in the header. If the header would not contain a supported language, the user would be redirected to https://www.example.com/my/path?lang=en.

If you use LangSelect::Cookie to register the plugin in auto mode or if you use LangSelect::fromCookie() to get and set the locale from a cookie manually at appropriate places in your application, the plugin will try to detect the locale from the cookie specified for the plugin.

bool MyApp::init()
{
// registering the plugin in manual mode and
// setting "lang" as default cookie name
auto lsp = new LangSelect(this);
lsp->setCookieName(QStringLiteral("lang"));
lsp->setSupportedLocales({
});
lsp->setFallbackLocale(QLocale(QLocale::English));
}
// now for example in the Auto method of the Root controller
// we will manually get and set the locale from the cookie
void Root::Auto(Contect *c)
{
LangSelect::fromCookie(c);
}

If a user now requests something on our site for the first time, there will be no cookie containing the language - neither set by auto detection from header nor by some input field. So the plugin will try to detect the language from the Accept-Language header and will store it to the cookie named "lang". On the next request it will not have to examine the header again but can take the locale from the cookie. As the session id is also stored as a cookie this approach is similar to the session approach but does not need a store for the session on the server side. If you use sessions anyway, you should store the locale in the session.

Session

If you use LangSelect::Session to register the plugin in auto mode or if you use LangSelect::fromSession() to get and set the locale from the session manually at appropriate places in your application, the plugin will try to detect the locale from the session key specified for the plugin. This approach is great if you use sessions anyway because the complete QLocale object can be stored in the session, making it quite fast to load compared with the other methods that have to construct the QLocale again from a string on every request.

bool MyApp::init()
{
// registering the plugin in auto mode using
// the session to store and retrieve the locale
auto lsp = new LangSelect(this, LangSelect::Session);
lsp->setSessionKey(QStringLiteral("lang"));
lsp->setSupportedLocales({
});
lsp->setFallbackLocale(QLocale(QLocale::English));
// in this example we will disable the detection from
// the Accept-Language header and will always use the
// fallback language if the locale could not be loaded
// from the session
lsp->setDetectFromHeader(false);
}

If a user now requests a resource in our application without having the locale stored in the session value identified by the "lang" key, the fallback locale English will be selected and will be stored to the session. For sure you would then need some selection interface that would make it possible for the user to change the display language. If you only would use the session to store the locale, it might be easier to store the locale in a cookie as that would need no session store.

Path

If you use a chained dispatcher to detect the locale, you can use this plugin in manual mode and use LangSelect::fromPath() at the chained action that takes the locale as path argument. This will then set the locale if it is supported or will redirect to a path containing a supported locale.

bool MyApp::init()
{
// register the plugin in manual mode and set
// the list of supported locales
auto lsp = new LangSelect(this);
lsp->setSupportedLocales({
});
lsp->setFallbackLocale(QLocale(QLocale::English));
}
void MyRoot::base(Context *c, const QString &locale)
{
if (LangSelect::fromPath(c, locale) {
// if the locale could be found in the
// list of supported locales, it will be set,
// otherwise the function will create a 307 redirect
// to the same path but with a valid locale and
// will call Context::detach()
}
}

If the user would now call the URL http://www.example.com/pt/my/resource the locale would be set to Portuguese and the normal flow of the application would continue. If the user would call the URL http://www.example.com/ru/my/resource and has no supported locale in the Accept-Language header, the function would create a redirect to http://www.example.com/en/my/resource and would detach from the normal execution flow.

Subdomain

If you use LangSelect::SubDomain to register the plugin in auto mode or if you use LangSelect::fromSubDomain() to get the locale from the subdomain manually at appropriate places in your application, the plugin will try to detect the locale from the subdomain part specified for the plugin. This approach needs for sure DNS entries for every supported locale. Let us assume we have the following registered subdomains: www.example.com, de.example.com, en.example.com and pt.example.com. The subdomain will simply be determined by checking if the domain starts with one of the entries from the map set by setSubDomainMap(). If the domain does not start with any of the map keys, the plugin will use the first domain part by splitting the domain name at the dots and will try to construct a valid QLocale object from it. If there can be no supported locale found, the plugin will use a locale from the Accept-Language header or the fallback locale. If for the example you want to use the fallback language English for the www subdomain, simply set setDetectFromHeader() to false.

bool MyApp::init()
{
auto lsp = new LangSelect(this, LangSelect::SubDomain);
// this will also set the list of supported locales
lsp->setSubDomainMap({
{QStringLiteral("de"), QLocale(QLocale::German)},
{QStringLiteral("en"), QLocale(QLocale::English)},
{QStringLiteral("pt"), QLocale(QLocale::Portuguese)}
]);
lsp->setFallbackLocale(QLocale(QLocale::English));
}

If a user now calls the URL http://www.exmaple.com the plugin will choose a locale from the Accept-Language header or the fallback locale. If the user would call http://pt.example.com Portuguese would be set as locale.

Domain

If you use LangSelect::Domain to register the plugin in auto mode or if you use LangSelect::fromDomain() to get the locale from the domain manually at appropriate places in your application, the plugin will try to detect the locale from the domain part specified for the plugin. This approach needs for sure DNS entries for every supported locale. Let us assume we have the following registered domains: exmaple.br, example.com, example.co.uk and example.de. The domain will simply be determined by checking if the domain ends with one of the keys from the map set by setDomainMap(). If the domain does not end with any of the map keys, the plugin will try to create a valid QLocale object from the TLD of the request URI. If that is not valid or not part of the supported locales, the plugin will try to detect the locale from the Accept-Language header (if setDetectFromHeader() has not been set to false) or will use the fallback language.

bool MyApp::init()
{
auto lsp = new LangSelect(this, LangSelect::Domain);
// this will also set the list of supported locales
lsp->setDomainMap({
{QStringLiteral("br"), QLocale(QLocale::Portuguese)},
{QStringLiteral("uk"), QLocale(QLocale::English,
QLocale::UnitedKingdom)}, {QStringLiteral("de"), QLocale(QLocale::German)}
});
lsp->setFallbackLocale(QLocale(QLocale::English));
}

If a user now calls the URL http://www.examble.br the locale will be set to Portuguese and the normal operation flow will continue. If there are also domains pointing to your application that are not part of the domain map

If you detect and set locales based on the session or a cookie you do not need to change anything on your links in your application. If you use the path or query to detect the locale, you can use Context::uriFor() or Context::uriForAction() to set the locale on internal URIs. For Grantlee themes there is also the Cutelyst specific tag c_uri_for that can be used as {% c_uri_for "/path" "arg1" "arg2" QUERY c.request.queryParams "foo=bar" %}. Taking the defaul name of the stash key with the BCP47 name of the selected locale and using the path approach to set the locale, you could use the tag as follows: {% c_uri_for "/path" c_langselect_lang "otherArg" QUERY c.request.queryParams "foo=bar" %}. If you use the domain or subdomain to set the locale, simply use relative paths in your internal links.

Configuration file options

There are some options you can set in your application configuration file in the Cutelyst_LangSelect_Plugin section. The configuration file options are available since Cutelyst 4.0.0.

cookie_expiration

Type: string
Default: 1 month

The expiration time of the cookie. The value will be parsed by Utils::durationFromString(), so you can use one of the supported human readable time spans.

cookie_domain

Type: string
Default: empty

The domain to be used when setting the cookie. When empty, the browser will set the current domain.

cookie_secure

Type: bool
Default: false

Whether to use a secure cookie. If this is set to true, the cookie will be marked as secure, which means browsers may ensure that the cookie is only sent with an HTTPS connection.

cookie_same_site

Type: string
Default: lax
Acceptable values: default,none,lax,strict

Defines the SameSite attribute of the cookie. See MDN to learn more about SameSite cookies. See also QNetworkCookie::SameSite.

Building and using

The plugin is linked to Cutelyst::Core, Cutelyst::Session and the QtNetwork module. To use it in your application, link your application to Cutelyst::Utils::LangSelect.

Logging category
cutelyst.plugin.langselect
Logging with Cutelyst
See also
Translate your application
Since
Cutelyst 2.1.0

Definition at line 348 of file langselect.h.

Member Function Documentation

◆ setup()

bool LangSelect::setup ( Application app)
overrideprotectedvirtual

Sets the plugin up and checks the plugin configuration. If the configuration contains errors, it will return false, otherwise it will return true. If the plugin has been constructed with auto detection constructor, it will connect the plugin to the Application::beforePrepareAction() signal.

Reimplemented from Cutelyst::Plugin.

Definition at line 52 of file langselect.cpp.

References Cutelyst::Application::beforePrepareAction(), QLocale::C, Qt::CaseInsensitive, QString::compare(), Cutelyst::Engine::config(), QObject::connect(), Cutelyst::Utils::durationFromString(), Cutelyst::Application::engine(), and Cutelyst::Application::postForked().