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 if ievv_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

str

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
  • url (str) – The URL to transform.

  • to_languagecode (str) – The languagecode to transform the url into.

  • from_languagecode (str) – The languagecode to transform the url from.

Returns

The transformed URL. If from_languagecode and to_languagecode is the same,

the provided url is returned unchanged.

Return type

str

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

urllib.parse.ParseResult

property scheme

Shortcut for parsed_url.scheme.

See parsed_url.

Returns

The url scheme (e.g.: https, http, …)

Return type

str

property netloc

Shortcut for parsed_url.netloc.

See parsed_url.

Returns

The url netloc (e.g.: www.example.com:9090)

Return type

str

property hostname

Shortcut for parsed_url.hostname.

See parsed_url.

Returns

The url hostname (e.g.: www.example.com)

Return type

str

property port

Shortcut for parsed_url.port.

See parsed_url.

Returns

The url port (e.g.: 8080, 80, …)

Return type

str

build_absolute_url(path_or_url)[source]

Build absolute URL from the provided path_or_url.

If the provided path_or_url is an URL, it is returned unchanged. If the provided path_or_url is a URL path, it is appended to the base url.

Parameters

path_or_url (str) – URL path or an URL.

Returns

[description]

Return type

[type]

i18n_url_settings

ievv_opensource.ievv_i18n_url.i18n_url_settings.get_fallback_base_url_setting()[source]

Get the IEVV_I18N_URL_FALLBACK_BASE_URL.

Raises

Exception – With a nice description if the setting is missing.

Returns

The value of the setting

Return type

str

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

str

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 overrides get_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

str

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

str

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

str

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 to active_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

ievv_opensource.ievv_i18n_url.templatetags.ievv_i18n_url_tags.transform_url_to_languagecode(context, *args, **kwargs)[source]

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 the request argument - we get that from context["request"].

Returns

An url.

Return type

str

handlers

UrlpathPrefixHandler

class ievv_opensource.ievv_i18n_url.handlers.UrlpathPrefixHandler[source]

I18n url handler that matches languages based on a URL prefix.

DjangoSessionHandler

class ievv_opensource.ievv_i18n_url.handlers.DjangoSessionHandler[source]

Django session based i18n url handler.

Warning

Please read the A warning about session based translations in the docs before deciding to use this handler.

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.

Parameters

languagecode (str) – Language code.

Returns

True if the provided languagecode is the default.

Return type

bool

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

str

property active_languagecode

Get the active languagecode.

Returns

The active languagecode.

Return type

str

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

str

property active_base_url

Get the active base url.

Returns

The active base url.

Return type

str

classmethod is_supported_languagecode(languagecode)[source]

Is the provided languagecode a supported languagecode?

Parameters

languagecode (str) – Language code.

Returns

True if the provided languagecode is supported.

Return type

bool

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.

Parameters

languagecode (str) – Language code.

Returns

Translated label for the languagecode.

Return type

str

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.

Parameters

languagecode (str) – Language code.

Returns

Unstranslated label for the languagecode.

Return type

str

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.

Parameters

languagecode (str) – Language code.

Returns

Local label for the languagecode.

Return type

str

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.

Parameters

languagecode (str) – Language code.

Returns

Label for the languagecode.

Return type

str

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:

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>
Parameters

languagecode (str) – Language code.

Returns

Icon css class

Return type

str

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.

Parameters

languagecode (str) – Language code.

Returns

SVG image URL.

Return type

str

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.

Parameters
  • path (str) – The path (same format as HttpRequest.get_full_path() returns - e.g: "/my/path?option1&option2")

  • languagecode (str, optional) – The languagecode to build the URI for. Defaults to None, which means we build the URI within the current languagecode.

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.

Parameters
  • path (str) – The path (same format as HttpRequest.get_full_path() returns - e.g: "/my/path?option1&option2")

  • languagecode (str, optional) – The languagecode to build the path for. Defaults to None, which means we build the URI within the current languagecode.

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.

Parameters
  • url (str) – The URL to transform.

  • languagecode (str) – The languagecode to transform the URL into.

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 provided languagecode.

Return type

str

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

set

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
Returns

Is the provided URL translatable?

Return type

bool

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

str

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
Returns

The current languagecode or None (None means default languagecode is detected).

Return type

str

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

str

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
Returns

The url path prefix. Can not start or end with /.

Return type

str

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.