ievv_i18n_url — i18n support for various ways of routing i18n views¶
Hosting a page in different languages can only be achived in a finite number of ways. These are the most common:
The default django way where the selected language is stored in session.
Some prefix to the URL path like
/<languagecode>/path/to/my/view
,/l/<languagecode>/path/to/my/view
, etc. typically with the default translation at/path/to/my/view
Based on domain name. E.g. myapp.com for english, myapp.no for norwegian, etc., or <languagecode>.myapp.com.
Combinations of prefix and separate domains (e.g.: you have custom domains for the most important languages, and just hos the rest on the “main” domain with prefix in the URL path).
ievv_i18n_url supports all these and more through:
Swappable URL handlers where all the logic happens (this is where these and more comes in since you can write your own handler class).
Template tags that use the URL handlers.
A library that provides a superset of the functionality of what the template tags provide.
Middleware that uses the handler to handle whatever you write a handler for.
Things this handle that the built-in locale URL support in Django does not handle:
Different default/fallback language per domain.
Support for domains to define the languagecode.
Locale aware URL reversing (e.g.: link to a page in a specific language).
Setup¶
Add it to settings:
INSTALLED_APPS = [
# ...
'ievv_opensource.ievv_i18n_url',
]
MIDDLEWARE = [
# ...
# Instead of django.middleware.locale.LocaleMiddleware, or any other LocaleMiddleware,
'ievv_opensource.ievv_i18n_url.middleware.LocaleMiddleware'
]
# Fallback base URL - used everywhere that we can not determine the "current" domain (management scripts that does not specify a base url etc).
IEVV_I18N_URL_FALLBACK_BASE_URL = https://mydomain.com/
# The handler class - see further down for the available handler classes
IEVV_I18N_URL_HANDLER = 'ievv_opensource.ievv_i18n_url.handlers.UrlpathPrefixHandler'
In your urls.py, wrap your URLs with i18n_patterns():
from ievv_opensource.ievv_i18n_url import i18n_url_utils
# Instead of:
# urlpatterns = [
# url(r'^my/view$', myview),
# url(r'^another/view$', anotherview),
# ]
# Wrap it in i18n_urlpatterns like this:
urlpatterns = i18n_url_utils.i18n_patterns(
url(r'^my/view$', myview),
url(r'^another/view$', anotherview),
)
Warning
You should not have ANY urls that are not wrapped with i18n_patterns - this will just make
the middleware and handlers work in an undeterministic manner. If you want to exclude URLs
from being translated, create a subclass of your handler and override
ievv_opensource.ievv_i18n_url.handlers.AbstractHandler.is_translatable_urlpath()
.
How it works¶
A very high level overview of how it works is that we have swappable handlers that decide what the current language is based on information they get from the request and URL.
The handlers¶
The handlers serve two puposes: - They have some classmehods that the LocaleMiddleware uses to set the current language. I.e.: The handlers
basically implement what the middleware should do.
They have a lot of helper methods to make it easy to work with locale aware URL schemes, and some methods to help generalize locale handling (such as labels and icons for languages).
The LocaleMiddleware¶
The middleware, ievv_opensource.ievv_i18n_url.middleware.LocaleMiddleware
, is very simple. It
just calls ievv_opensource.ievv_i18n_url.handlers.AbstractHandler.activate_languagecode_from_request()
on the configured handler, and then it sets some information about the detected language on the request:
request.LANGUAGE_CODE
: Set to the languagecode we activated django translations for.request.session['LANGUAGE_CODE']
: Same value as request.LANGUAGE_CODE - this is just set for compatibility with code that is written explicitly for session based language selection.request.IEVV_I18N_URL_DEFAULT_LANGUAGE_CODE
: Set to the detected default language code.request.IEVV_I18N_URL_ACTIVE_LANGUAGE_CODE
: Set to the active languagecode. This is normally the same as request.LANGUAGE_CODE, but ifievv_opensource.ievv_i18n_url.handlers.AbstractHandler.get_translation_to_activate_for_languagecode()
is overridden on the handler they may differ. E.g.: Multiple languages may use the same Django translation.
Url pattern handling¶
The ievv_opensource.ievv_i18n_url.i18n_url_utils.i18n_patterns()
function that
you wrap around all your URL patterns uses a custom URL resolver that
simply gets the languagecode that the middleware set, and ignores any URL path
prefix that the handler has said that we have for the current language. E.g.:
we use the same “hack/smart solution” as the built in i18n url routing handler in Django.
A warning about session based translations¶
We provide support for session based translations, BUT it is not fully supported. Things like generating an URL for a specific languagecode, or finding the translation for the current URL in a different languagecode is NOT possible to do in a safe manner.
The reason for this limitation is that ALL translations live at the same URL, and the only way to safely change the languagecode is to HTTP POST a change to the languagecode. You may want to implement a handler that ignores this and actually provides full support with session based translations. This will require some kind of redirect view that changes the session language from a HTTP GET request.
Our recommendation is to NOT use session based translations, and instead use a URL path or domain based translation handler.
Utilities¶
i18n_url_utils¶
-
ievv_opensource.ievv_i18n_url.i18n_url_utils.
get_handler_class
()[source]¶ Get the configured ievv_i18n_url handler class.
E.g. import the handler class from the class path configured in the
IEVV_I18N_URL_HANDLER
setting.- Returns
A handler class.
- Return type
ievv_opensource.ievv_i18n_url.handlers.abstract_handler.AbstractHandler
-
ievv_opensource.ievv_i18n_url.i18n_url_utils.
i18n_reverse
(viewname, urlconf=None, args=None, kwargs=None, current_app=None, languagecode=None)[source]¶ Serves kind of the same use case as the
django.urls.reverse
function, but with i18n URL support, AND this function returns an absolute URL.The reason why it returns absolute URL is because i18n URLs may be based on domains, not just URL paths.
NOTE: Session based ievv_i18n_url handlers will ignore the languagecode argument and just return the URL for the default translation. This is because all their translations live at the same URL. See the A warning about session based translations in the docs for more details.
- Parameters
viewname – See the docs for
django.urls.reverse
.urlconf – See the docs for
django.urls.reverse
. Defaults to None.args – See the docs for
django.urls.reverse
. Defaults to None.kwargs – See the docs for
django.urls.reverse
. Defaults to None.current_app – See the docs for
django.urls.reverse
. Defaults to None.languagecode (str, optional) – The languagecode to reverse the URL in. Defaults to None, which means we reverse the URL in the current languagecode.
- Returns
An URL.
- Return type
-
ievv_opensource.ievv_i18n_url.i18n_url_utils.
transform_url_to_languagecode
(url, to_languagecode, from_languagecode=None)[source]¶ Takes an URL, and finds the URL to the same content within a different languagecode.
NOTE: Session based ievv_i18n_url handlers will ignore the languagecode argument and just return provided url. This is because all their translations live at the same URL. See the A warning about session based translations in the docs for more details.
- Parameters
- Returns
- The transformed URL. If from_languagecode and to_languagecode is the same,
the provided
url
is returned unchanged.
- Return type
-
ievv_opensource.ievv_i18n_url.i18n_url_utils.
i18n_patterns
(*urls, include_redirect_view=True)[source]¶ Adds the language code prefix to every URL pattern within this function. This may only be used in the root URLconf, not in an included URLconf.
-
class
ievv_opensource.ievv_i18n_url.i18n_url_utils.
I18nRegexURLResolver
(urlconf_name, default_kwargs=None, app_name=None, namespace=None, prefix_default_language=False)[source]¶ A URL resolver that always matches the active language code as URL prefix.
Rather than taking a regex argument, we just override the
regex
function to always return the active language-code as regex.
base_url¶
-
class
ievv_opensource.ievv_i18n_url.base_url.
BaseUrl
(url_or_urllib_parseresult)[source]¶ Defines ievv_i18n_url base url.
The base URL is the URL to the root of the domain e.g: https://example.com, http://www.example.com:8080, etc. (NOT https://example.com/my/path, https://example.com/en, etc.).
The constructor can take any valid absolute URL, or None (in which case it falls back on the IEVV_I18N_URL_FALLBACK_BASE_URL setting), but all the methods and properties work on a
urllib.parse.ParseResult
that does not have any of the URL parts after-
parsed_url
¶ Get the parsed URL.
The returned URL only has scheme+domain+port (e.g.: scheme+netloc).
- Returns
A parsed URL on the same format as urllib.parse.urlparse returns.
- Return type
-
property
scheme
¶ Shortcut for parsed_url.scheme.
See
parsed_url
.- Returns
The url scheme (e.g.: https, http, …)
- Return type
-
property
netloc
¶ Shortcut for parsed_url.netloc.
See
parsed_url
.- Returns
The url netloc (e.g.: www.example.com:9090)
- Return type
-
property
hostname
¶ Shortcut for parsed_url.hostname.
See
parsed_url
.- Returns
The url hostname (e.g.: www.example.com)
- Return type
-
property
port
¶ Shortcut for parsed_url.port.
See
parsed_url
.- Returns
The url port (e.g.: 8080, 80, …)
- Return type
-
i18n_url_settings¶
active_i18n_url_translation¶
-
ievv_opensource.ievv_i18n_url.active_i18n_url_translation.
get_default_languagecode
()[source]¶ Get the default language code activated within the current thread.
I.e.: This returns the default languagecode that the
ievv_opensource.ievv_i18n_url.middleware.LocaleMiddleware
sets as default.If this is called without using the middleware, or in management scripts etc. where the middleware is not applied, we fall back on
settings.LANGUAGE_CODE
.- Returns
The default languagecode for the current thread.
- Return type
-
ievv_opensource.ievv_i18n_url.active_i18n_url_translation.
set_default_languagecode
(default_languagecode)[source]¶ Used by
ievv_opensource.ievv_i18n_url.middleware.LocaleMiddleware
to set the default languagecode in the current thread.Warning
You will normally not want to use this, but it may be useful in management scripts along with calling
activate()
.
-
ievv_opensource.ievv_i18n_url.active_i18n_url_translation.
get_active_languagecode
()[source]¶ Get the active language code activated within the current thread.
I.e.: This returns the active languagecode that the
ievv_opensource.ievv_i18n_url.middleware.LocaleMiddleware
sets as active. This may not be the same as the languagecode django.utils.translation.get_language() returns if the handler overridesget_translation_to_activate_for_languagecode()
.If this is called without using the middleware, or in management scripts etc. where the middleware is not applied, we fall back on
settings.LANGUAGE_CODE
.- Returns
The active languagecode for the current thread.
- Return type
-
ievv_opensource.ievv_i18n_url.active_i18n_url_translation.
set_active_languagecode
(active_languagecode)[source]¶ Used by
ievv_opensource.ievv_i18n_url.middleware.LocaleMiddleware
to set the active languagecode in the current thread.Warning
You will normally not want to use this, but it may be useful in management scripts along with calling
activate()
.
-
ievv_opensource.ievv_i18n_url.active_i18n_url_translation.
get_active_language_urlpath_prefix
()[source]¶ Get the active URL path prefix within the current thread.
I.e.: This returns the language url path prefix that the
ievv_opensource.ievv_i18n_url.middleware.LocaleMiddleware
sets as active.If this is called without using the middleware, or in management scripts etc. where the middleware is not applied, we fall back on empty string.
- Returns
The URL path prefix for active language in the current thread.
- Return type
-
ievv_opensource.ievv_i18n_url.active_i18n_url_translation.
set_active_language_urlpath_prefix
(urlpath_prefix)[source]¶ Used by
ievv_opensource.ievv_i18n_url.middleware.LocaleMiddleware
to set the active language url prefix in the current thread.Warning
You will normally not want to use this, but it may be useful in management scripts along with calling
activate()
.
-
ievv_opensource.ievv_i18n_url.active_i18n_url_translation.
get_active_base_url
()[source]¶ Get the default language code activated within the current thread.
I.e.: This returns the language url path prefix that the
ievv_opensource.ievv_i18n_url.middleware.LocaleMiddleware
sets as active.If this is called without using the middleware, or in management scripts etc. where the middleware is not applied, we fall back on empty string.
- Returns
The URL path prefix for active language in the current thread.
- Return type
-
ievv_opensource.ievv_i18n_url.active_i18n_url_translation.
set_active_base_url
(active_base_url)[source]¶ Used by
ievv_opensource.ievv_i18n_url.middleware.LocaleMiddleware
to set the active language url prefix in the current thread.Warning
You will normally not want to use this, but it may be useful in management scripts along with calling
activate()
.
-
ievv_opensource.ievv_i18n_url.active_i18n_url_translation.
activate
(active_languagecode, default_languagecode, active_translation_languagecode=None, active_base_url=None, active_language_urlpath_prefix=None)[source]¶ Activate a translation.
This works much like
django.utils.translation.activate()
(and it calls that function), but it stores all of the stuff ievv_i18n_url needs to function in the current thread context.- Parameters
active_languagecode (str) – Language code to set as the active languagecode in the current thread.
default_languagecode (str) – Default language code for the current thread.
active_translation_languagecode (str) – Language code to set as the active translation in the current thread. I.e.: The languagecode we send to
django.utils.translation.activate()
. Defaults toactive_languagecode
.active_base_url (urllib.parse.ParseResult) – The active base URL (E.g.: https://example.com/). Defaults to settings.IEVV_I18N_URL_FALLBACK_BASE_URL. Can be provided as a urllib.parse.ParseResult or as a string.
active_language_urlpath_prefix (str) – URL path prefix for the active language.
Template tags¶
Template tag for the ievv_i18n_utils.transform_url_to_languagecode function.
See
transform_url_to_languagecode()
for the available arguments, but do not provide therequest
argument - we get that fromcontext["request"]
.- Returns
An url.
- Return type
handlers¶
UrlpathPrefixHandler¶
DjangoSessionHandler¶
AbstractHandler¶
-
class
ievv_opensource.ievv_i18n_url.handlers.
AbstractHandler
[source]¶ Base class for ievv_i18n_url handlers.
-
is_default_languagecode
(languagecode)[source]¶ Is the provided languagecode the default language code?
Note that this may be per domain etc. depending on the handler class.
-
property
default_languagecode
¶ Get the default language code.
Note that this may be per domain etc. depending on the handler class.
Defaults to
settings.LANGUAGE_CODE
.- Returns
Default languagecode.
- Return type
-
property
active_languagecode
¶ Get the active languagecode.
- Returns
The active languagecode.
- Return type
-
property
active_languagecode_or_none_if_default
¶ Get the active languagecode, but returns None if the active languagecode is the default languagecode.
- Returns
The active languagecode, or None if the active languagecode is the default languagecode.
- Return type
-
classmethod
is_supported_languagecode
(languagecode)[source]¶ Is the provided languagecode a supported languagecode?
-
get_translated_label_for_languagecode
(languagecode)[source]¶ Get the translated label for the languagecode (the name of the language) in the currently active language.
This defaults to the english name for the language fetched via
django.utils.translation.get_language_info()
.This is typically used in subclasses that override
get_label_for_languagecode()
and change to translated labels by default.
-
get_untranslated_label_for_languagecode
(languagecode)[source]¶ Get the untranslated label for the languagecode (the name of the language).
Should return a label for the languagecode in a commonly used language that most users of your site will understand.
This defaults to the english name for the language fetched via
django.utils.translation.get_language_info()
.This is the what the default implementation of
get_label_for_languagecode()
uses.
-
get_local_label_for_languagecode
(languagecode)[source]¶ Get the local label for the languagecode (the name of the language in that language).
This defaults to the english name for the language fetched via
django.utils.translation.get_language_info()
.This is typically used in subclasses that override
get_label_for_languagecode()
and change to labels in the native language by default.
-
get_label_for_languagecode
(languagecode)[source]¶ Get the label for the languagecode (the name of the language).
Defaults to using
get_local_label_for_languagecode()
. I.e.: We use the native/local translation of the language name as language label by default.
-
classmethod
activate_languagecode_from_request
(request)[source]¶ Activate the detected languagecode.
This is what
ievv_opensource.ievv_i18n_url.middleware.LocaleMiddleware
uses to process the request.What this does:
Builds a base_url using
request.build_absolute_uri('/')
.Finds the default language code using
detect_default_languagecode()
Finds the current language code using
detect_current_languagecode()
Handles fallback to default languagecode if the current languagecode is unsupported or the requested url path is not not translatable (using
is_supported_languagecode()
andis_translatable_urlpath()
).Finds the language URL prefix
get_urlpath_prefix_for_languagecode()
.Activates the current language/translation using
ievv_opensource.ievv_i18n_url.active_i18n_url_translation.activate()
.
Warning
Do not override this method, and you should normally not call this method. I.e.: This is for the middleware.
-
get_icon_cssclass_for_languagecode
(languagecode)[source]¶ Get an icon CSS class for the language code.
This is typically implemented on a per app basis. I.e.: The application creates a subclass of one of the built-in handlers and override this to provide icons for their supported languages. This is provided as part of the handler to make it possible to generalize things like rendering language selects with icons.
This icon must be possible to use in HTML like this:
<span class="ICON_CSS_CLASS_HERE"></span>
-
get_icon_svg_image_url_for_languagecode
(languagecode)[source]¶ Get an icon SVG image URL for the language code.
This is typically implemented on a per app basis. I.e.: The application creates a subclass of one of the built-in handlers and override this to provide icons for their supported languages. This is provided as part of the handler to make it possible to generalize things like rendering language selects with icons.
-
build_absolute_url
(path, languagecode=None)[source]¶ Build absolute uri for the provided path within the provided languagecode.
MUST be implemented in subclasses.
Note
Session based handlers will ignore the languagecode argument and just return the URL for the default translation. This is because all their translations live at the same URL. See the A warning about session based translations in the docs for more details.
-
build_urlpath
(path, languagecode=None)[source]¶ Build URL path for the provided path within the provided languagecode.
This is a compatibility layer to make it possible to work with older code that considers a URL path as fully qualified. Most handlers will just do nothing with the path, or prepend a prefix, but some handlers (those that work with multiple domains), will use the
ievv_i18n_url_redirect_to_languagecode
redirect view here to return an URL that will redirect the user to the correct URL.MUST be implemented in subclasses.
Note
Session based handlers will ignore the languagecode argument and just return the PATH for the default translation. This is because all their translations live at the same URL. See the A warning about session based translations in the docs for more details.
-
transform_url_to_languagecode
(url, languagecode)[source]¶ Transform the provided url into the “same” url, but in the provided languagecode.
MUST be implemented in subclasses.
Note
This is not possible to implement in a safe manner for session based handlers (I.e.: multiple translation live at the same URI), so for these kind of handler this method will just return the provided url. See the A warning about session based translations in the docs for more details.
-
classmethod
get_translation_to_activate_for_languagecode
(languagecode)[source]¶ Get the languagecode to actually activate for the provided languagecode.
Used by the middleware provided by ievv_i18n_url to activate the translation for the provided languagecode.
This is here to make it possible for applications to have languages that has their own domains or URLs, but show translations from another language code. E.g.: you may have content in a language, but be OK with translation strings from another language.
- Returns
The languagecode to activate with the django translation system for the provided
languagecode
. Defaults to the providedlanguagecode
.- Return type
-
classmethod
get_supported_languagecodes
()[source]¶ Get supported language codes.
Defaults to the language codes in
settings.LANGUAGES
.- Returns
A set of the supported language codes.
- Return type
-
classmethod
is_translatable_urlpath
(base_url, path)[source]¶ Is the provided URL path translatable within the current base_url?
We default to consider the paths in settings.MEDIA_URL and settings.STATIC_URL as untranslatable.
If this returns
False
, the middleware will use the default translation when serving the path.If you subclass this, you should not write code that parses the querystring part of the path. This will not work as intended (will not work the same everywhere) since the middleware does not call this with the querystring included, but other code using this may pass in the path with the querystring.
- Parameters
base_url (ievv_opensource.ievv_i18n_url.base_url.BaseUrl) – The base URL - see
ievv_opensource.ievv_i18n_url.base_url.BaseUrl
for more info.path (str) – An URL path (e.g:
/my/path
,/my/path?a=1&b=2
, etc.)
- Returns
Is the provided URL translatable?
- Return type
-
classmethod
detect_preferred_languagecode_for_user
(user)[source]¶ Detect the preferred languagecode for the provided user.
This is normally NOT used by
detect_current_languagecode()
except for handlers like the session handler where the URL does not change based on language code. E.g.: It would be strange to serve a language based on user preferences when the URL explicitly says what language code we are serving.This is mostly a convenience for management scripts, and a utility if you want to redirect users based on the preferred language code.
- Parameters
user – A user model object.
- Returns
The preferred language code for the provided user, or None. None means that the user has no preferred language code, or that the handler does not respect user preferences. Returns
None
by default.- Return type
-
classmethod
detect_current_languagecode
(base_url, request)[source]¶ Detect the current languagecode from the provided request and/or base_url.
Used by the middleware provided by ievv_i18n_url find the current language code and set it on the current request.
DO NOT USE THIS - it is for the middleware. Use
current_languagecode
.MUST be overridden in subclasses.
If this returns None, it means that we should use the default languagecode. I.e.: Do not handle fallback to default languagecode when implementing this method in subclasses - just return None.
- Parameters
base_url (ievv_opensource.ievv_i18n_url.base_url.BaseUrl) – The base URL - see
ievv_opensource.ievv_i18n_url.base_url.BaseUrl
for more info.request (django.http.HttpRequest) – The HttpRequest
- Returns
The current languagecode or None (None means default languagecode is detected).
- Return type
-
classmethod
detect_default_languagecode
(base_url)[source]¶ Detect the default languagecode for the provided base_url.
This is here so that handlers can override it to support different default languagecode per domain or perhaps more fancy stuff based on the provided url.
- Parameters
base_url (django.http.HttpRequest) – The base URL - e.g: https://example.com, http://www.example.com:8080, … (NOT https://example.com/my/path, https://example.com/en, …).
- Returns
The default languagecode. Defaults to
settings.LANGUAGE_CODE
.- Return type
-
classmethod
get_urlpath_prefix_for_languagecode
(base_url, languagecode)[source]¶ Get the URL path prefix for the provided languagecode within the current base_url.
- Parameters
base_url (ievv_opensource.ievv_i18n_url.base_url.BaseUrl) – The base URL - see
ievv_opensource.ievv_i18n_url.base_url.BaseUrl
for more info.languagecode (str) – The language code to find the prefix for.
- Returns
The url path prefix. Can not start or end with
/
.- Return type
-
Middleware¶
-
class
ievv_opensource.ievv_i18n_url.middleware.
LocaleMiddleware
(get_response=None)[source]¶ ievv_js_i18n_url locale middleware.
-
response_redirect_class
¶ alias of
django.http.response.HttpResponseRedirect
-
process_request
(request)[source]¶ Initializes the ievv_i18n_url handler from the request, and calls
ievv_opensource.ievv_i18n_url.handlers.AbstractHandler.activate_languagecode_from_request()
.- Parameters
request – The request-object.
-