Commits

Patrick Samson  committed 10147cc

Initial upload

  • Participants

Comments (0)

Files changed (11)

+glob:*.pyc
+glob:_build
+Copyright (c) 2010, Patrick Samson.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, 
+       this list of conditions and the following disclaimer.
+    
+    2. Redistributions in binary form must reproduce the above copyright 
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+
+    3. Neither the name of django-robot-locale nor the names of its contributors
+       may be used to endorse or promote products derived from this software
+       without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+===================
+Django Robot Locale
+===================
+
+This is an application for the Django web framework.
+It acts as a wrapper for a multilingual web site, for varying the URLs
+for each supported language.
+It is not intended for human users, but for search engines, in order for
+the site to be indexed in more than one single language.
+
+See the docs/ directory for Sphinx documentation.
+For example, build the HTML version with >make html
+and open docs/_build/html/index.html
+
+Copyright (C) 2010, Patrick Samson
+This program is licensed under the BSD License (see the file LICENSE).

File docs/Makefile

+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html      to make standalone HTML files"
+	@echo "  dirhtml   to make HTML files named index.html in directories"
+	@echo "  pickle    to make pickle files"
+	@echo "  json      to make JSON files"
+	@echo "  htmlhelp  to make HTML files and a HTML help project"
+	@echo "  qthelp    to make HTML files and a qthelp project"
+	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  changes   to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck to check all external links for integrity"
+	@echo "  doctest   to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-robot-locale.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-robot-locale.qhc"
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+	      "run these through (pdf)latex."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."

File docs/conf.py

+# -*- coding: utf-8 -*-
+#
+# django-robot-locale documentation build configuration file, created by
+# sphinx-quickstart on Mon Aug 16 09:46:19 2010.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.append(os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = []
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'django-robot-locale'
+copyright = u'2010, Patrick Samson'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '1.0'
+# The full version, including alpha/beta/rc tags.
+release = '1.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'django-robot-localedoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'django-robot-locale.tex', u'django-robot-locale Documentation',
+   u'Patrick Samson', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True

File docs/howitworks.rst

+How it works
+============
+
+The application acts as a middleware, in two steps.
+
+On the incoming way:
+
+    If the requested URL begins with a supported prefix language, this prefix is withdrawn,
+    so it will not appear to the URL patterns dispatcher. And the so detected language is activated.
+    
+    URLs must not have a domain part and must begin with a '/' character, which is the way they are
+    built by the Django's built-in tag `url <http://docs.djangoproject.com/en/dev/ref/templates/builtins/#url>`_.
+
+On the outgoing way:
+
+    Processing is performed only if a language has been detected on the incoming way.
+
+    In the contents:
+
+        Except for the URLs excluded by the ``ROBOT_LOCALE_EXCLUDES`` setting,
+        the language code is added as a prefix to:
+
+        * the ``href`` attribute of ``<a>`` elements
+        * the ``action`` attribute of ``<form>`` elements
+
+    For a redirect instruction:
+
+        In normal case, the language code is added as a prefix to the target location.
+
+        For the redirections initiated by a POST to a URL ending with a suffix declared in
+        the ``ROBOT_LOCALE_SETLANG_ENDS`` setting, the possible language prefix is erased
+        from the target location.
+
+Examples:
+
++----------------------------------+-------------------------------------------+----------------------------------------+
+| Request                          | Response from the view                    | Response to the client                 |
++==================================+===========================================+========================================+
+| GET **/en**/app/page1/           | ...<a href="/app/page2/">                 | ...<a href="**/en**/app/page2/">       |
+|                                  | another page</a>...                       | another page</a>...                    |
++----------------------------------+-------------------------------------------+----------------------------------------+
+| **POST** /en/i18n/**setlang/** , | Location: \http://d.com/**en**/app/page1/ | Location: \http://d.com/app/page1/     |
+| from /en/app/page1/              |                                           |                                        |
++----------------------------------+-------------------------------------------+----------------------------------------+
+| GET **/en**/app/page1/           | Location: /app/page2/                     | Location: **/en**/app/page2/           |
++----------------------------------+-------------------------------------------+----------------------------------------+
+
+    

File docs/index.rst

+.. django-robot-locale documentation master file, created by
+   sphinx-quickstart on Mon Aug 16 09:46:19 2010.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to django-robot-locale's documentation!
+===============================================
+
+This is an application for `Django <http://www.djangoproject.com>`_-powered websites.
+
+Natively, Django has some features to set up a multilingual version of your site:
+`i18n support`_, a `locale middleware`_, etc.
+So it works perfectly as a "many translations / one URL" scheme. It's very confortable for backend applications, such as
+the Admin site. And even for the public facing contents: if you configure the preferred languages in your browser,
+you will be served automatically localized pages.
+But this mechanism has a drawback: your pages can be found by search engines only in the site's default language.
+There is no way for a robot to index one URL multiple times, even if there would be different contents, especially the language used.
+This may be a major penalty for websites targeting an international audience.
+
+.. _`i18n support`: http://docs.djangoproject.com/en/dev/topics/i18n/internationalization/
+.. _`locale middleware`: http://docs.djangoproject.com/en/dev/topics/i18n/deployment/
+
+The aim of this application is to automate the varying of URLs with a language prefix,
+without the need to change anything to the applications of the website, which is a major advantage
+when the site uses reusable third-party applications.
+
+For instance, if you have ``someapp`` installed, serving ``someview``, for your english & french multilingual website,
+these URLs are available:
+
+Usual address for human visitors:
+
+* \http://www.somedomain.com/someapp/someview/
+
+Additional addresses for robots:
+
+* \http://www.somedomain.com/**en**/someapp/someview/
+* \http://www.somedomain.com/**fr**/someapp/someview/
+
+The application is compatible with ``django.middleware.locale.LocaleMiddleware``.
+
+----
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+
+   quickstart
+   howitworks
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+

File docs/make.bat

+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+set SPHINXBUILD=sphinx-build
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+	:help
+	echo.Please use `make ^<target^>` where ^<target^> is one of
+	echo.  html      to make standalone HTML files
+	echo.  dirhtml   to make HTML files named index.html in directories
+	echo.  pickle    to make pickle files
+	echo.  json      to make JSON files
+	echo.  htmlhelp  to make HTML files and a HTML help project
+	echo.  qthelp    to make HTML files and a qthelp project
+	echo.  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+	echo.  changes   to make an overview over all changed/added/deprecated items
+	echo.  linkcheck to check all external links for integrity
+	echo.  doctest   to run all doctests embedded in the documentation if enabled
+	goto end
+)
+
+if "%1" == "clean" (
+	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+	del /q /s %BUILDDIR%\*
+	goto end
+)
+
+if "%1" == "html" (
+	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+	goto end
+)
+
+if "%1" == "dirhtml" (
+	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+	goto end
+)
+
+if "%1" == "pickle" (
+	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+	echo.
+	echo.Build finished; now you can process the pickle files.
+	goto end
+)
+
+if "%1" == "json" (
+	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+	echo.
+	echo.Build finished; now you can process the JSON files.
+	goto end
+)
+
+if "%1" == "htmlhelp" (
+	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+	echo.
+	echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+	goto end
+)
+
+if "%1" == "qthelp" (
+	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+	echo.
+	echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-robot-locale.qhcp
+	echo.To view the help file:
+	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-robot-locale.ghc
+	goto end
+)
+
+if "%1" == "latex" (
+	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+	echo.
+	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+	goto end
+)
+
+if "%1" == "changes" (
+	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+	echo.
+	echo.The overview file is in %BUILDDIR%/changes.
+	goto end
+)
+
+if "%1" == "linkcheck" (
+	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+	echo.
+	echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+	goto end
+)
+
+if "%1" == "doctest" (
+	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+	echo.
+	echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+	goto end
+)
+
+:end

File docs/quickstart.rst

+Quick start guide
+=================
+
+Requisites and dependances
+--------------------------
+
+Python version >= 2.6
+
+Reasons :
+
+* use of ``str.format()``
+
+Installation
+------------
+Get the code from the repository, which is hosted at `Bitbucket <http://bitbucket.org/>`_.
+
+You have two main ways to obtain the latest code and documentation:
+
+With the version control software Mercurial installed, get a local copy by typing::
+
+    hg clone http://bitbucket.org/psam/django-robot-locale/
+
+Or download a copy of the package, which is available in several compressed formats,
+either from the ``Download`` tab or from the ``get source`` menu option.
+
+In both case, make sure the directory is accessible from the Python import path.
+
+Configuration
+-------------
+
+Add ``robot_locale`` to the ``INSTALLED_APPS`` setting of your project.
+
+Add ``robot_locale.middleware.RobotLocaleMiddleware`` to the ``MIDDLEWARE_CLASSES`` setting of your project.
+
+    **Caution**: Order is important.
+    
+    Make sure you place the ``RobotLocaleMiddleware`` after the ``django.middleware.locale.LocaleMiddleware`` if you use it.
+    This is needed because the Django's middleware will act as a usual default setter and the ``RobotLocaleMiddleware``,
+    if necessary, will supersede the language setting.
+
+There is no need to run a ``manage.py syncdb``, since this application has no model.
+
+Optional settings
+~~~~~~~~~~~~~~~~~
+
+You may specify some additional configuration options in your ``settings.py``:
+
+``ROBOT_LOCALE_LANGUAGES``
+    The list of language codes to be considered by the application.
+    
+    *Defaults to*: ``settings.LANGUAGE_CODE`` + any language code available under the ``locale/`` directory of the project.
+
+``ROBOT_LOCALE_SETLANG_ENDS``
+    A suffix as a string or a tuple of suffixes.
+    When a POST to a URL ending with such a suffix returns a redirect response, the possible language prefix will be erased from the location.
+    
+    *Defaults to*: ``/setlang/``, the pattern in ``django.conf.urls.i18n``
+
+``ROBOT_LOCALE_EXCLUDES``
+    Declaration of some URLs that will remain untouched.
+    
+    *Reduced syntax*: If the value is not a dictionary, it will be considered as the ``exact`` entry of the dictionary of the full syntax.
+    
+    *Full syntax*: A dictionary with two keys:
+    
+        * ``exact`` for an exact match
+        * ``start`` for matching the start of the URL
+        
+    Each value may be a string or an iterable of strings.
+    
+    Examples:
+        | ``'/'``
+        | ``('/', '/unique/')``
+        | ``{ 'start': '/someapp' }``
+        | ``{ 'exact': '/', 'start': ['/someprefix', '/someapp'] }``
+    
+    *Defaults to*: no exclusion.
+    
+    For example, with ``'/'``, if a request to ``/en/somepage/`` returns in its content ``<a href="/">Home</a>``,
+    it will **not** be converted to ``href="/en/"``, but preserved as ``href="/"``.
+    So the further navigation will revert to the normal usual language detection, if any.
+
+Usage
+-----
+
+You have to give to robots an entry point as a localized URL for each target language.
+One point should be enough, it's up to the robot to explore the whole site further.
+There is no need to make the links available to the user.
+For human browsing, `the set_language redirect view`_ is more appropriate.
+
+.. _`the set_language redirect view`: http://docs.djangoproject.com/en/dev/topics/i18n/internationalization/#the-set-language-redirect-view
+
+For example, put something like this somewhere in the root page of the site::
+
+    <div style="display: none">
+    {% for lang in LANGUAGES %}
+    <a href="/{{ lang.0 }}/">{% trans lang.1 %}</a>
+    {% endfor %}
+    </div>
+
+**Tip**:
+    Suppose your visitor came on your site by following a localized URL obtained from a search engine.
+    It may not be his favorite language, so it would be nice to offer the opportunity to escape from this 
+    never ending enforced language.
+    There are two ways:
+
+    * Make use of `the set_language redirect view`_.
+      But it may be inappropriate, or even too intrusive, to change some pages.
+    * Define some neutral links, i.e. links leading to non-localized URLs. The top home page is a good candidate of that purpose.
+      This goal is achieved with the ``ROBOT_LOCALE_EXCLUDES`` setting.
+
+Sample
+------
+
+``settings.py``::
+
+    LANGUAGE_CODE = 'en'
+    gettext = lambda s: s
+    LANGUAGES = (
+        ('en', gettext('English')),
+        ('fr', gettext('French')),
+    )
+    MIDDLEWARE_CLASSES = (
+        ...
+        'django.middleware.locale.LocaleMiddleware',
+        'robot_locale.middleware.RobotLocaleMiddleware',
+        ...
+    )
+    INSTALLED_APPS = (
+        ...
+        'robot_locale',
+        ...
+    )
+    ROBOT_LOCALE_EXCLUDES = '/' # allows a way to step out from the prefix loop
+
+With this file system context::
+
+    mysite/
+     |_ locale/
+         |_ fr/
+             |_ LC_MESSAGES/
+                 |_ django.mo
+
+The considered language prefixes in URLs will be: ``/en`` and ``/fr``, the same as with this optional setting::
+
+    ROBOT_LOCALE_LANGUAGES = ('en', 'fr')
+

File robot_locale/__init__.py

Empty file added.

File robot_locale/middleware.py

+import os
+import re
+import urlparse
+
+from django.conf import settings
+from django.utils import translation
+from django.utils.cache import patch_vary_headers
+from django.utils.importlib import import_module
+
+LOCALES = set([settings.LANGUAGE_CODE])
+# look for additional available languages of the project
+if settings.SETTINGS_MODULE is not None:    # (as in django.utils.translation.translation())
+    parts = settings.SETTINGS_MODULE.split('.')
+    # This way works as well:
+    # projectpath = os.path.join(imp.find_module(parts[0])[1], 'locale')
+    # But is not the preferred one because:
+    # 1. Having the parent directory in the Python path is mandatory, which is usually
+    #    forgotten in development environment (django manage.py has a trick to
+    #    compensate for this missing)
+    # 2. The filesystem is accessed even though this module is already loaded
+    #    at this stage
+    # 3. Importing an already imported module is faster
+    project = import_module(parts[0])
+    projectpath = os.path.join(os.path.dirname(project.__file__), 'locale')
+    for file in os.listdir(projectpath):
+        if os.path.isdir(os.path.join(projectpath, file)) and os.path.exists(os.path.join(projectpath, file, 'LC_MESSAGES', 'django.mo')):
+            LOCALES.add(file)
+LOCALES = tuple(getattr(settings, 'ROBOT_LOCALE_LANGUAGES', LOCALES))
+SETLANG_ENDS = getattr(settings, 'ROBOT_LOCALE_SETLANG_ENDS', '/setlang/') # defaults to what is in django.conf.urls.i18n
+excludes = getattr(settings, 'ROBOT_LOCALE_EXCLUDES')   # a dict, a string, or an iterable of strings
+if isinstance(excludes, dict):
+    exact = excludes.get('exact')
+    start = excludes.get('start')
+else:
+    exact = excludes
+    start = None
+if isinstance(exact, basestring): exact = [exact]
+if isinstance(start, basestring): start = [start]
+EXCLUDES = []
+if exact:
+    EXCLUDES = [ i+'\B' for i in exact]
+if start:
+    EXCLUDES.extend(start)
+
+# language prefix detection
+PREFIX_RE = re.compile(r'^/(?P<locale>{0})(?P<path>.*)$'.format('|'.join(LOCALES)))
+# in redirect headers, to add the prefix, if not already there
+LOCATION_ADD_RE = re.compile(r'^(/)(?!({0}))(.*$)'.format('|'.join([l + '/' for l in LOCALES])), re.IGNORECASE)
+# in redirect headers, to substract the prefix, if present
+LOCATION_SUB_RE = re.compile(r'^(/)({0})(.*$)'.format('|'.join([l + '/' for l in LOCALES])), re.IGNORECASE)
+# spot urls to enrich on output
+URLS_RE = re.compile(r'(<(?:a[^>]+href|form[^>]+action)=["\'])(?=/)(?!({0}))([^>]*>)'.format('|'.join(['/'+ l + '/' for l in LOCALES]+EXCLUDES)), re.IGNORECASE)
+
+def split_path(path):
+    """
+    Try to split the path in two parts: an language prefix and a final path.
+    Returns them as a tuple.
+    
+    '/'        ->  ''  , '/'
+    '/foo/'    ->  ''  , '/foo/'
+    '/frfoo/'  ->  ''  , '/frfoo/'
+    '/fr'      ->  'fr', '/'
+    '/fr/foo/' ->  'fr', '/foo/'
+    """
+    m = PREFIX_RE.match(path)
+    if m:
+        real_path = m.group('path') or '/'
+        if real_path.startswith('/'):
+            return m.group('locale'), real_path
+    return '', path
+
+class RobotLocaleMiddleware(object):
+    """
+    Middleware that sets the language if a possible supported language prefix
+    is found in the request path. If so, this prefix is stripped of the path
+    and it will be added to URLs at the response step.
+    
+    For example, the path '/en/foo/' will set request.LANGUAGE_CODE to 'en'
+    and request.path_info to '/foo/'. On output a '<a href="/page/">' will be
+    changed to '<a href="/en/page/">'.
+    """
+
+    def process_request(self, request):
+        locale, path = split_path(request.path_info)
+        self.locale = locale
+        if locale:
+            request.path_info = path
+            translation.activate(locale)
+            request.LANGUAGE_CODE = translation.get_language()
+
+    def process_response(self, request, response):
+        if self.locale:
+            patch_vary_headers(response, ('Accept-Language',))
+            if 'Content-Language' not in response:
+                response['Content-Language'] = translation.get_language()
+            translation.deactivate()
+            if (response.status_code == 301 or response.status_code == 302):
+                sr = urlparse.urlsplit(response['Location'])
+                path = sr.path
+                if request.method == 'POST' and request.path_info.endswith(SETLANG_ENDS):
+                    # django.views.i18n.set_language uses HTTP_REFERER, which is an absolute URL with domain name
+                    path = LOCATION_SUB_RE.sub(r'\1\3', path)
+                else:
+                    path = LOCATION_ADD_RE.sub(r'\1{0}/\3'.format(self.locale), path)
+                response['Location'] = urlparse.urlunsplit((sr.scheme,sr.netloc,path,sr.query,sr.fragment))
+            else:
+                response.content = URLS_RE.sub(r'\1/{0}\3'.format(self.locale), response.content)
+        return response