[ckan-changes] commit/ckan: 3 new changesets

Bitbucket commits-noreply at bitbucket.org
Tue Oct 25 11:50:01 UTC 2011


3 new commits in ckan:


https://bitbucket.org/okfn/ckan/changeset/a3c8d4f7cf91/
changeset:   a3c8d4f7cf91
branch:      defect-1418-i18n
user:        dread
date:        2011-10-25 13:04:00
summary:     [controllers,lib]: Lots of i18n fixes: #1374 switch to english, #1417 browser detection, #1388 etags on home page.
affected #:  18 files

diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 ckan/config/deployment.ini_tmpl
--- a/ckan/config/deployment.ini_tmpl
+++ b/ckan/config/deployment.ini_tmpl
@@ -179,6 +179,16 @@
 #ckan.recaptcha.publickey = 
 #ckan.recaptcha.privatekey = 
 
+# Locale/languages
+# TODO: Figure out a nicer way to get this. From the .ini? 
+# Order these by number of people speaking it in Europe:
+# http://en.wikipedia.org/wiki/Languages_of_the_European_Union#Knowledge
+# (or there abouts)
+ckan.locale_default = en
+#ckan.locales_offered = 
+ckan.locale_order = en de fr it es pl ru nl sv no cs_CZ hu pt_BR fi bg ca sq
+ckan.locales_filtered_out = el
+
 # Logging configuration
 [loggers]
 keys = root, ckan, ckanext


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 ckan/controllers/home.py
--- a/ckan/controllers/home.py
+++ b/ckan/controllers/home.py
@@ -2,13 +2,14 @@
 import sys
 
 from pylons import cache, config
+from pylons.i18n import set_lang
 from genshi.template import NewTextTemplate
 import sqlalchemy.exc
 
 from ckan.authz import Authorizer
 from ckan.logic import NotAuthorized
 from ckan.logic import check_access, get_action
-from ckan.i18n import set_session_locale, set_lang
+from ckan.lib.i18n import set_session_locale, get_lang
 from ckan.lib.search import query_for, QueryOptions, SearchError
 from ckan.lib.cache import proxy_cache, get_cache_expires
 from ckan.lib.base import *
@@ -41,16 +42,11 @@
             
 
     @staticmethod
-    def _home_cache_key(latest_revision_id=None):
-        '''Calculate the etag cache key for the home page. If you have
-        the latest revision id then supply it as a param.'''
-        if not latest_revision_id:
-            latest_revision_id = model.repo.youngest_revision().id
+    def _home_cache_key():
+        '''Calculate the etag cache key for the home page.'''
         user_name = c.user
-        if latest_revision_id:
-            cache_key = str(hash((latest_revision_id, user_name)))
-        else:
-            cache_key = 'fresh-install'
+        language = get_lang()
+        cache_key = str(hash((user_name, language)))
         return cache_key
 
     @proxy_cache(expires=cache_expires)
@@ -86,6 +82,11 @@
                 abort(400, _('Invalid language specified'))
             try:
                 set_lang(locale)
+                # NOTE: When translating this string, substitute the word
+                # 'English' for the language being translated into.
+                # We do it this way because some Babel locales don't contain
+                # a display_name!
+                # e.g. babel.Locale.parse('no').get_display_name() returns None
                 h.flash_notice(_("Language has been set to: English"))
             except:
                 h.flash_notice("Language has been set to: English")


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 ckan/controllers/package.py
--- a/ckan/controllers/package.py
+++ b/ckan/controllers/package.py
@@ -7,7 +7,7 @@
 from sqlalchemy.orm import eagerload_all
 import genshi
 from pylons import config, cache
-from pylons.i18n import get_lang, _
+from pylons.i18n import _
 from autoneg.accept import negotiate
 from babel.dates import format_date, format_datetime, format_time
 
@@ -25,6 +25,7 @@
 from ckan.logic import NotFound, NotAuthorized, ValidationError
 from ckan.logic import tuplize_dict, clean_dict, parse_params, flatten_to_string_key
 from ckan.lib.dictization import table_dictize
+from ckan.lib.i18n import get_lang
 import ckan.forms
 import ckan.authz
 import ckan.rating
@@ -179,7 +180,8 @@
     def _pkg_cache_key(pkg):
         # note: we need pkg.id in addition to pkg.revision.id because a
         # revision may have more than one package in it.
-        return str(hash((pkg.id, pkg.latest_related_revision.id, c.user, pkg.get_average_rating())))
+        language = get_lang()
+        return str(hash((pkg.id, pkg.latest_related_revision.id, c.user, pkg.get_average_rating(), language)))
 
     @proxy_cache()
     def read(self, id):


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 ckan/i18n/__init__.py
--- a/ckan/i18n/__init__.py
+++ b/ckan/i18n/__init__.py
@@ -1,65 +0,0 @@
-from pylons.i18n import _, add_fallback, get_lang, set_lang, gettext
-from babel import Locale
-
-
-# TODO: Figure out a nicer way to get this. From the .ini? 
-# Order these by number of people speaking it in Europe:
-# http://en.wikipedia.org/wiki/Languages_of_the_European_Union#Knowledge
-# (or there abouts)
-_KNOWN_LOCALES = ['en',
-                  'de',
-                  'fr',
-                  'it',
-                  'es',
-                  'pl',
-                  'ru',
-                  'nl',
-                  'sv', # Swedish
-                  'no',
-#                  'el', # Greek
-                  'cs_CZ',
-                  'hu',
-                  'pt_BR',
-                  'fi', 
-                  'bg',
-                  'ca',
-                  'sq', 
-                  ]
-
-def get_available_locales():
-    return map(Locale.parse, _KNOWN_LOCALES)
-
-def get_default_locale():
-    from pylons import config
-    return Locale.parse(config.get('ckan.locale')) or \
-            Locale.parse('en')
-
-def set_session_locale(locale):
-    if locale not in _KNOWN_LOCALES:
-        raise ValueError
-    from pylons import session
-    session['locale'] = locale
-    session.save()
-
-def handle_request(request, tmpl_context):
-    from pylons import session
-
-    tmpl_context.language = locale = None
-    if 'locale' in session:
-        locale = Locale.parse(session.get('locale'))
-    else:
-        requested = [l.replace('-', '_') for l in request.languages]
-        locale = Locale.parse(Locale.negotiate(_KNOWN_LOCALES, requested))
-
-    if locale is None:
-        locale = get_default_locale()
-    
-    options = [str(locale), locale.language, str(get_default_locale()),
-        get_default_locale().language]
-    for language in options:
-        try:
-            set_lang(language) 
-            tmpl_context.language = language
-        except: pass
-
-


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 ckan/i18n/ckan.pot
--- a/ckan/i18n/ckan.pot
+++ b/ckan/i18n/ckan.pot
@@ -214,6 +214,7 @@
 msgid "Invalid language specified"
 msgstr ""
 
+#. NOTE: Substitute 'English' for the language being translated into.
 #: ckan/controllers/home.py:89
 msgid "Language has been set to: English"
 msgstr ""


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 ckan/lib/base.py
--- a/ckan/lib/base.py
+++ b/ckan/lib/base.py
@@ -20,7 +20,7 @@
 
 import ckan
 from ckan import authz
-from ckan import i18n
+from ckan.lib import i18n
 import ckan.lib.helpers as h
 from ckan.plugins import PluginImplementations, IGenshiStreamFilter
 from ckan.lib.helpers import json


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 ckan/lib/helpers.py
--- a/ckan/lib/helpers.py
+++ b/ckan/lib/helpers.py
@@ -19,7 +19,7 @@
 from routes import url_for, redirect_to
 from alphabet_paginate import AlphaPage
 from lxml.html import fromstring
-from ckan.i18n import get_available_locales
+from i18n import get_available_locales
 
 
 


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 ckan/lib/i18n.py
--- /dev/null
+++ b/ckan/lib/i18n.py
@@ -0,0 +1,201 @@
+import os
+
+import pylons
+from pylons.i18n import _, add_fallback, set_lang, gettext, LanguageError
+from pylons.i18n.translation import _get_translator
+from babel import Locale, localedata
+from babel.core import LOCALE_ALIASES
+
+import ckan.i18n
+
+def singleton(cls):
+    instances = {}
+    def getinstance():
+        if cls not in instances:
+            instances[cls] = cls()
+        return instances[cls]
+    return getinstance
+
+i18n_path = os.path.dirname(ckan.i18n.__file__)
+
+ at singleton
+class Locales(object):
+    def __init__(self):
+        from pylons import config
+
+        # Get names of the locales
+        # (must be a better way than scanning for i18n directory?)
+        known_locales = ['en'] + [locale_name for locale_name in os.listdir(i18n_path) \
+                                  if localedata.exists(locale_name)]
+        self._locale_names, self._default_locale_name = self._work_out_locales(
+            known_locales, config)
+        self._locale_objects = map(Locale.parse, self._locale_names)
+        self._default_locale_object = Locale.parse(self._default_locale_name)
+
+        self._aliases = LOCALE_ALIASES
+        self._aliases['pt'] = 'pt_BR' # Default Portuguese language to
+                                     # Brazilian territory, since
+                                     # we don't have a Portuguese territory
+                                     # translation currently.
+
+    def _work_out_locales(self, known_locales, config_dict):
+        '''Work out the locale_names to offer to the user and the default locale.
+        All locales in this method are strings, not Locale objects.'''
+        # pass in a config_dict rather than ckan.config to make this testable
+
+        # Get default locale
+        assert not config_dict.get('lang'), \
+               '"lang" config option not supported - please use ckan.locale_default instead.'
+        default_locale = config_dict.get('ckan.locale_default') or \
+                         config_dict.get('ckan.locale') or \
+                         None # in this case, set it later on
+        if default_locale:
+            assert default_locale in known_locales
+
+        # Filter and reorder locales by config options
+        def get_locales_in_config_option(config_option):
+            locales_ = config_dict.get(config_option, '').split()
+            if locales_:
+                unknown_locales = set(locales_) - set(known_locales)
+                assert not unknown_locales, \
+                       'Bad config option %r - locales not found: %s' % \
+                       (config_option, unknown_locales)
+            return locales_
+        only_locales_offered = get_locales_in_config_option('ckan.locales_offered')
+        if only_locales_offered:
+            locales = only_locales_offered
+        else:
+            locales = known_locales
+            
+        def move_locale_to_start_of_list(locale_):
+            if locale_ not in locales:
+                raise ValueError('Cannot find locale "%s" in locales offered.' % locale_)
+            locales.pop(locales.index(locale_))
+            locales.insert(0, locale_)
+            
+        locales_filtered_out = get_locales_in_config_option('ckan.locales_filtered_out')
+        for locale in locales_filtered_out:
+            try:
+                locales.pop(locales.index(locale))
+            except ValueError, e:
+                raise ValueError('Could not filter out locale "%s" from offered locale list "%s": %s') % \
+                      (locale, locales, e)
+
+        locale_order = get_locales_in_config_option('ckan.locale_order')
+        if locale_order:
+            for locale in locale_order[::-1]:
+                # bring locale_name to the front
+                try:
+                    move_locale_to_start_of_list(locale)
+                except ValueError, e:
+                    raise ValueError('Could not process ckan.locale_order options "%s" for offered locale list "%s": %s' % \
+                                     (locale_order, locales, e))
+        elif default_locale:
+            if default_locale not in locales:
+                raise ValueError('Default locale "%s" is not amongst locales offered: %s' % \
+                                 (default_locale, locales))
+            # move the default locale to the start of the list
+            try:
+                move_locale_to_start_of_list(default_locale)
+            except ValueError, e:
+                raise ValueError('Could not move default locale "%s" to the start ofthe list of offered locales "%s": %s' % \
+                                 (default_locale, locales, e))
+
+        assert locales
+            
+        if not default_locale:
+            default_locale = locales[0]
+        assert default_locale in locales
+
+        return locales, default_locale
+
+    def get_available_locales(self):
+        '''Returns a list of the locale objects for which translations are
+        available.'''
+        return self._locale_objects
+
+    def get_available_locale_names(self):
+        '''Returns a list of the locale strings for which translations are
+        available.'''
+        return self._locale_names
+
+    def get_default_locale(self):
+        '''Returns the default locale/language as specified in the CKAN
+        config. It is a locale object.'''
+        return self._default_locale_object
+
+    def get_aliases(self):
+        '''Returns a mapping of language aliases, like the Babel LOCALE_ALIASES
+        but with hacks for specific CKAN issues.'''
+        return self._aliases
+
+    def negotiate_known_locale(self, preferred_locales):
+        '''Given a list of preferred locales, this method returns the best
+        match locale object from the known ones.'''
+        assert isinstance(preferred_locales, (tuple, list))
+        preferred_locales = [str(l).replace('-', '_') for l in preferred_locales]
+        return Locale.parse(Locale.negotiate(preferred_locales,
+                                             self.get_available_locale_names(),
+                                             aliases=self.get_aliases()
+                                             ))
+
+def get_available_locales():
+    return Locales().get_available_locales()
+
+def set_session_locale(locale):
+    if locale not in get_available_locales():
+        raise ValueError
+    from pylons import session
+    session['locale'] = locale
+    session.save()
+
+def handle_request(request, tmpl_context):
+    from pylons import session
+
+    # Work out what language to show the page in.
+    locales = [] # Locale objects. Ordered highest preference first.
+    tmpl_context.language = None
+    if session.get('locale'):
+        # First look for locale saved in the session (by home controller)
+        locales.append(Locale.parse(session.get('locale')))
+    else:
+        # Next try languages in the HTTP_ACCEPT_LANGUAGE header
+        locales.append(Locales().negotiate_known_locale(request.languages))
+
+    # Next try the default locale in the CKAN config file
+    locales.append(Locales().get_default_locale())
+
+    locale = set_lang_list(locales)
+    tmpl_context.language = locale.language
+    return locale
+
+def set_lang_list(locales):
+    '''Takes a list of locales (ordered by reducing preference) and tries
+    to set them in order. If one fails then it puts up a flash message and
+    tries the next.'''
+    import ckan.lib.helpers as h
+    failed_locales = set()
+    for locale in locales:
+        # try locales in order of preference until one works
+        try:
+            if str(locale) == 'en':
+                # There is no language file for English, so if we set_lang
+                # we would get an error. Just don't set_lang and finish.
+                break
+            set_lang(str(locale))
+            break
+        except LanguageError, e:
+            if str(locale) not in failed_locales:
+                h.flash_error('Could not change language to %r: %s' % \
+                              (str(locale), e))
+                failed_locales.add(str(locale))
+    return locale
+
+def get_lang():
+    '''Returns the current language. Based on babel.i18n.get_lang but works
+    when set_lang has not been run (i.e. still in English).'''
+    langs = pylons.i18n.get_lang()
+    if langs:
+        return langs[0]
+    else:
+        return 'en'


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 ckan/tests/functional/test_home.py
--- a/ckan/tests/functional/test_home.py
+++ b/ckan/tests/functional/test_home.py
@@ -1,4 +1,5 @@
-from pylons import c
+from pylons import c, session
+from pylons.i18n import set_lang
 
 from ckan.lib.create_test_data import CreateTestData
 from ckan.controllers.home import HomeController
@@ -21,11 +22,14 @@
     def teardown_class(self):
         model.repo.rebuild_db()
 
+    def clear_language_setting(self):
+        self.app.cookies = {}
+
     def test_home_page(self):
         offset = url_for('home')
         res = self.app.get(offset)
-        print res
         assert 'Add a dataset' in res
+        assert 'Could not change language' not in res
 
     def test_calculate_etag_hash(self):
         c.user = 'test user'
@@ -38,11 +42,11 @@
         hash_3 = get_hash()
         assert hash_2 != hash_3
 
-        model.repo.new_revision()
-        model.Session.add(model.Package(name=u'test_etag'))
-        model.repo.commit_and_remove()
-        hash_4 = get_hash()
-        assert hash_3 != hash_4
+        # I can't get set_lang to work and deliver correct
+        # result to get_lang, so leaving it commented
+##        set_lang('fr')
+##        hash_4 = get_hash()
+##        assert hash_3 != hash_4
 
     @search_related
     def test_packages_link(self):
@@ -63,6 +67,36 @@
         res = self.app.get(offset)
         assert '<strong>TEST TEMPLATE_FOOTER_END TEST</strong>'
 
+    def test_locale_detect(self):
+        offset = url_for('home')
+        self.clear_language_setting()
+        res = self.app.get(offset, headers={'Accept-Language': 'de,pt-br,en'})
+        try:
+            assert 'Willkommen' in res.body, res.body
+        finally:
+            self.clear_language_setting()
+
+    def test_locale_negotiate(self):
+        offset = url_for('home')
+        self.clear_language_setting()
+        res = self.app.get(offset, headers={'Accept-Language': 'fr-ca'})
+        # Request for French with Canadian territory should negotiate to
+        # just 'fr'
+        try:
+            assert 'propos' in res.body, res.body
+        finally:
+            self.clear_language_setting()
+
+    def test_locale_negotiate_pt(self):
+        offset = url_for('home')
+        self.clear_language_setting()
+        res = self.app.get(offset, headers={'Accept-Language': 'pt'})
+        # Request for Portuguese should find pt_BR because of our alias hack
+        try:
+            assert 'Bem-vindo' in res.body, res.body
+        finally:
+            self.clear_language_setting()
+
     def test_locale_change(self):
         offset = url_for('home')
         res = self.app.get(offset)
@@ -71,7 +105,7 @@
             res = res.follow()
             assert 'Willkommen' in res.body
         finally:
-            res = res.click('English')
+            self.clear_language_setting()
 
     def test_locale_change_invalid(self):
         offset = url_for(controller='home', action='locale', locale='')
@@ -106,9 +140,7 @@
             res = res.goto(href)
             assert res.status == 200, res.status # doesn't redirect
         finally:
-            offset = url_for('home')
-            res = self.app.get(offset)
-            res = res.click('English')
+            self.clear_language_setting()
 
 class TestDatabaseNotInitialised(TestController):
     @classmethod


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 ckan/tests/lib/test_i18n.py
--- /dev/null
+++ b/ckan/tests/lib/test_i18n.py
@@ -0,0 +1,154 @@
+from nose.tools import assert_equal, assert_raises
+from babel import Locale
+from pylons import config, session
+import pylons
+from pylons.i18n import get_lang
+
+from ckan.lib.i18n import Locales, set_session_locale, set_lang
+import ckan.lib.i18n
+
+from ckan.tests.pylons_controller import PylonsTestCase, TestSession
+
+class TestLocales:
+    def test_work_out_locales__thedatahub(self):
+        # as it is (roughly) on thedatahub.org
+        locales, default = Locales()._work_out_locales(
+            ['en', 'fr', 'de'],
+            {'ckan.locale': 'en'})
+        assert_equal(locales, ['en', 'fr', 'de'])
+        assert_equal(default, 'en')
+
+    def test_work_out_locales__france(self):
+        # as it is (roughly) on a foreign language site
+        locales, default = Locales()._work_out_locales(
+            ['en', 'fr', 'de'],
+            {'ckan.locale': 'fr'})
+        # fr moved to start of the list
+        assert_equal(locales, ['fr', 'en', 'de'])
+        assert_equal(default, 'fr')
+
+    def test_work_out_locales__locales_offered(self):
+        locales, default = Locales()._work_out_locales(
+            ['en', 'fr', 'de'],
+            {'ckan.locales_offered': 'fr de'})
+        assert_equal(locales, ['fr', 'de'])
+        assert_equal(default, 'fr')
+
+    def test_work_out_locales__locales_order(self):
+        locales, default = Locales()._work_out_locales(
+            ['en', 'fr', 'de'],
+            {'ckan.locale': 'fr',
+             'ckan.locale_order': 'de fr en'})
+        assert_equal(locales, ['de', 'fr', 'en'])
+        assert_equal(default, 'fr')
+
+    def test_work_out_locales__locales_filtered_out(self):
+        locales, default = Locales()._work_out_locales(
+            ['en', 'fr', 'de'],
+            {'ckan.locale': 'fr',
+             'ckan.locales_filtered_out': 'de'})
+        assert_equal(locales, ['fr', 'en'])
+        assert_equal(default, 'fr')
+        
+    def test_work_out_locales__default(self):
+        # don't specify default lang and it is not en,
+        # so default to next in list.
+        locales, default = Locales()._work_out_locales(
+            ['en', 'fr', 'de'],
+            {'ckan.locale': 'fr',
+             'ckan.locales_filtered_out': 'en'})
+        assert_equal(locales, ['fr', 'de'])
+        assert_equal(default, 'fr')
+
+    def test_work_out_locales__bad_default(self):
+        assert_raises(ValueError, Locales()._work_out_locales, 
+            ['en', 'fr', 'de'],
+            {'ckan.locale': 'en',
+             'ckan.locales_offered': 'fr de'})
+
+    def test_get_available_locales(self):
+        locales = Locales().get_available_locales()
+        assert len(locales) > 5, locales
+        locale = locales[0]
+        assert isinstance(locale, Locale)
+
+        locales_str = set([str(locale) for locale in locales])
+        langs = set([locale.language for locale in locales])
+        assert set(('en', 'de', 'cs_CZ')) < locales_str, locales_str
+        assert set(('en', 'de', 'cs')) < langs, langs
+
+    def test_default_locale(self):
+        # This should be setup in test-core.ini
+        assert_equal(config.get('ckan.locale_default'), 'en')
+        default_locale = Locales().get_default_locale()
+        assert isinstance(default_locale, Locale)
+        assert_equal(default_locale.language, 'en')
+
+    def test_negotiate_known_locale(self):
+        # check exact matches always work
+        locales = Locales().get_available_locales()
+        for locale in locales:
+            result = Locales().negotiate_known_locale([locale])
+            assert_equal(result, locale)
+
+        assert_equal(Locales().negotiate_known_locale(['en_US']), 'en')
+        assert_equal(Locales().negotiate_known_locale(['en_AU']), 'en')
+        assert_equal(Locales().negotiate_known_locale(['es_ES']), 'es')
+        assert_equal(Locales().negotiate_known_locale(['pt']), 'pt_BR')
+
+class TestI18n(PylonsTestCase):
+    def test_set_session_locale(self):
+        set_session_locale('en')
+        assert_equal(session['locale'], 'en')
+
+        set_session_locale('fr')
+        assert_equal(session['locale'], 'fr')
+
+    def handle_request(self, session_language=None, languages_header=[]):
+        session['locale'] = session_language
+        class FakePylons:
+            translator = None
+        class FakeRequest:
+            # Populated from the HTTP_ACCEPT_LANGUAGE header normally
+            languages = languages_header
+            # Stores details of the translator
+            environ = {'pylons.pylons': FakePylons()}
+        request = FakeRequest()
+        real_pylons_request = pylons.request
+        try:
+            pylons.request = request # for set_lang to work
+            class FakeTmplContext:
+                language = None # gets filled in by handle_request
+            tmpl_context = FakeTmplContext()
+            ckan.lib.i18n.handle_request(request, tmpl_context)
+            return tmpl_context.language # the language that got set
+        finally:
+            pylons.request = real_pylons_request
+    
+    def test_handle_request__default(self):
+        assert_equal(self.handle_request(),
+                     'en')
+        
+    def test_handle_request__session(self):
+        assert_equal(self.handle_request(session_language='fr'),
+                     'fr')
+
+    def test_handle_request__header(self):
+        assert_equal(self.handle_request(languages_header=['de']),
+                     'de')
+
+    def test_handle_request__header_negotiate(self):
+        # Language so is not an option, so reverts to next one
+        assert_equal(self.handle_request(languages_header=['so_KE', 'de']),
+                     'de')
+
+    def test_handle_request__header_but_defaults(self):
+        # Language so is not an option, so reverts to default
+        assert_equal(self.handle_request(languages_header=['so_KE']),
+                     'en')
+
+    def test_handle_request__header_territory(self):
+        # Request for specific version of German ends up simply as de.
+        assert_equal(self.handle_request(languages_header=['fr_CA', 'en']),
+                     'fr')
+        


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 doc/api.rst
--- a/doc/api.rst
+++ b/doc/api.rst
@@ -556,4 +556,4 @@
 Action API
 ~~~~~~~~~~
 
-See: 
\ No newline at end of file
+See: :doc:`apiv3`
\ No newline at end of file


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 doc/common-error-messages.rst
--- /dev/null
+++ b/doc/common-error-messages.rst
@@ -0,0 +1,75 @@
+Common error messages
+---------------------
+
+Whether a developer runs CKAN using paster or going through CKAN test suite, there are a number of error messages seen that are the result of setup problems. As people experience them, please add them to the list here.
+
+``nose.config.ConfigError: Error reading config file 'setup.cfg': no such option 'with-pylons'``
+================================================================================================
+
+   This error can result when you run nosetests for two reasons:
+
+   1. Pylons nose plugin failed to run. If this is the case, then within a couple of lines of running `nosetests` you'll see this warning: `Unable to load plugin pylons` followed by an error message. Fix the error here first.
+
+   2. The Python module 'Pylons' is not installed into you Python environment. Confirm this with::
+
+        python -c "import pylons"
+
+``OperationalError: (OperationalError) no such function: plainto_tsquery ...``
+==============================================================================
+
+   This error usually results from running a test which involves search functionality, which requires using a PostgreSQL database, but another (such as SQLite) is configured. The particular test is either missing a `@search_related` decorator or there is a mixup with the test configuration files leading to the wrong database being used.
+
+``ImportError: No module named worker``
+=======================================
+
+   The python entry point for the worker has not been generated. This occurs during the 'pip install' of the CKAN source, and needs to be done again if switching from older code that didn't have it. To recitify it::
+
+        python setup.py egg_info
+
+``ImportError: cannot import name get_backend``
+===============================================
+
+   This can be caused by an out of date pyc file. Delete all your pyc files and start again::
+
+        find . -name "*.pyc" | xargs rm
+
+``ImportError: cannot import name UnicodeMultiDict``
+====================================================
+
+   This is caused by using a version of WebOb that is too new (it has deprecated UnicodeMultiDict). Check the version like this (ensure you have activated your python environment first)::
+
+         pip freeze | grep -i webob
+
+   Now install the version specified in requires/lucid_present.txt. e.g.::
+
+         pip install webob==1.0.8
+
+``nosetests: error: no such option: --ckan``
+============================================
+
+   Nose is either unable to find ckan/ckan_nose_plugin.py in the python environment it is running in, or there is an error loading it. If there is an error, this will surface it::
+
+         nosetests --version
+
+   There are a few things to try to remedy this:
+
+   Commonly this is because the nosetests isn't running in the python environment. You need to have nose actually installed in the python environment. To see which you are running, do this::
+
+         which nosetests
+
+   If you have activated the environment and this still reports ``/usr/bin/nosetests`` then you need to::
+
+         pip install --ignore-installed nose
+
+   If ``nose --version`` still fails, ensure that ckan is installed in your environment::
+
+         cd pyenv/src/ckan
+         python setup.py develop
+
+   One final check - the version of nose should be at least 1.0. Check with::
+
+         pip freeze | grep -i nose
+
+``AttributeError: 'unicode' object has no attribute 'items'`` (Cookie.py)
+=========================================================================
+


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 doc/configuration.rst
--- a/doc/configuration.rst
+++ b/doc/configuration.rst
@@ -194,18 +194,54 @@
 -----------------------------
 
 .. index::
-   single: lang
+   single: ckan.locale_default
 
-lang
-^^^^
+ckan.locale_default
+^^^^^^^^^^^^^^^^^^^
 
 Example::
 
- lang=de
+ ckan.locale_default=de
 
 Default value:  ``en`` (English)
 
-Use this to specify the language of the text displayed in the CKAN web UI. This requires a suitable `mo` file installed for the language. For more information on internationalization, see :doc:`i18n`.
+Use this to specify the locale (language of the text) displayed in the CKAN Web UI. This requires a suitable `mo` file installed for the locale in the ckan/i18n. For more information on internationalization, see :doc:`i18n`. If you don't specify a default locale, then it will default to the first locale offered, which is by default English (alter that with `ckan.locales_offered` and `ckan.locales_filtered_out`.
+
+.. note: In versions of CKAN before 1.5, the settings used for this was variously `lang` or `ckan.locale`, which have now been deprecated in favour of `ckan.locale_default`.
+
+ckan.locales_offered
+^^^^^^^^^^^^^^^^^^^^
+
+Example::
+
+ ckan.locales_offered=en de fr
+
+Default value: (none)
+
+By default, all locales found in the ckan/i18n directory will be offered to the user. To only offer a subset of these, list them under this option. The ordering of the locales is preserved when offered to the user.
+
+ckan.locales_filtered_out
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Example::
+
+ ckan.locales_filtered_out=pl ru
+
+Default value: (none)
+
+If you want to not offer particular locales to the user, then list them here to have them removed from the options.
+
+ckan.locale_order
+^^^^^^^^^^^^^^^^^
+
+Example::
+
+ ckan.locale_order=fr de
+
+Default value: (none)
+
+If you want to specify the ordering of all or some of the locales as they are offered to the user, then specify them here in the required order. Any locales that are available but not specified in this option, will still be offered at the end of the list.
+
 
 Theming Settings
 ----------------


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 doc/i18n.rst
--- a/doc/i18n.rst
+++ b/doc/i18n.rst
@@ -2,16 +2,18 @@
 Internationalize CKAN
 =====================
 
-CKAN is used in many countries, and adding a new language is a simple process. 
+CKAN is used in many countries, and adding a new language to the web interface is a simple process. 
+
+.. Note: Storing metadata field values in more than one language is a separate topic. This is achieved by storing the translations in extra fields. A custom dataset form and dataset display template are recommended. Ask the CKAN team for more information.
 
 Supported Languages
 ===================
 
 CKAN already supports numerous languages. To check whether your language is supported, look in the source at ``ckan/i18n`` for translation files. Languages are named using two-letter ISO language codes (e.g. ``es``, ``de``).
 
-If your language is present, you can switch language simply by setting the ``lang`` option in your CKAN config file, as described in :ref:`config-i18n`. For example, to switch to German::
+If your language is present, you can switch the default language simply by setting the ``ckan.locale_default`` option in your CKAN config file, as described in :ref:`config-i18n`. For example, to switch to German::
 
- lang=de
+ ckan.locale_default=de
 
 If your language is not supported yet, the remainder of this section section provides instructions on how to prepare a translation file and add it to CKAN. 
 
@@ -20,8 +22,36 @@
 
 If you want to add an entirely new language to CKAN, you have two options.
 
-* :ref:`i18n-manual`. Creating translation files manually.  
 * :ref:`i18n-transifex`. Creating translation files using Transifex, the open source translation software. 
+* :ref:`i18n-manual`. Creating translation files manually.
+
+
+.. _i18n-transifex:
+
+Transifex Setup
+---------------
+
+Transifex, the open translation platform, provides a simple web interface for writing translations and is widely used for CKAN internationalization. 
+
+Using Transifex makes it easier to handle collaboration, with an online editor that makes the process more accessible.
+
+Existing CKAN translation projects can be found at: https://www.transifex.net/projects/p/ckan/teams/
+
+Updated translations are automatically pushed to https://bitbucket.org/bboissin/ckan-i18n and these can be compiled and placed on CKAN servers by the server administrators.
+
+Transifex Administration
+++++++++++++++++++++++++
+
+The Transifex workflow is as follows:
+
+* Install transifex command-line utilities
+* ``tx init`` in CKAN to connect to Transifex
+* Run ``python setup.py extract_messages`` on the CKAN source
+* Upload the local .pot file via command-line ``tx push``
+* Get people to complete translations on Transifex
+* Pull locale .po files via ``tx pull``
+* ``python setup.py compile_catalog``
+* Mercurial Commit and push po and mo files
 
 
 .. _i18n-manual:
@@ -29,7 +59,9 @@
 Manual Setup
 ------------
 
-If you prefer not to use Transifex, you can create translation files manually.
+If you prefer not to use Transifex, you can create translation files manually. 
+
+.. note:: Please keep the CKAN core developers aware of new languages created in this way.
 
 All the English strings in CKAN are extracted into the ``ckan.pot`` file, which can be found in ``ckan/i18n``.
 
@@ -112,32 +144,5 @@
 6. Configure the Language
 +++++++++++++++++++++++++
 
-Finally, once the mo file is in place, you can switch between the installed languages using the ``lang`` option in the CKAN config file, as described in :ref:`config-i18n`. 
+Finally, once the mo file is in place, you can switch between the installed languages using the ``ckan.locale`` option in the CKAN config file, as described in :ref:`config-i18n`. 
 
-
-.. _i18n-transifex:
-
-Transifex Setup
----------------
-
-Transifxes, the open translation platform, provides a simple web interface for writing translations and is widely used for CKAN internationalization. 
-
-Using Transifex makes it easier to handle collaboration, with an online editor that makes the process more accessible.
-
-Existing CKAN translation projects can be found at: https://www.transifex.net/projects/p/ckan/teams/
-
-Updated translations are automatically pushed to https://bitbucket.org/bboissin/ckan-i18n and these can be compiled and placed on CKAN servers by the server administrators.
-
-Transifex Administration
-++++++++++++++++++++++++
-
-The Transifex workflow is as follows:
-
-* Install transifex command-line utilities
-* ``tx init`` in CKAN to connect to Transifex
-* Run ``python setup.py extract_messages`` on the CKAN source
-* Upload the local .pot file via command-line ``tx push``
-* Get people to complete translations on Transifex
-* Pull locale .po files via ``tx pull``
-* ``python setup.py compile_catalog``
-* Commit and push po and mo files


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 doc/index.rst
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -33,6 +33,7 @@
    configuration
    api
    test
+   common-error-messages
    buildbot
    about
 


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 doc/install-from-source.rst
--- a/doc/install-from-source.rst
+++ b/doc/install-from-source.rst
@@ -295,3 +295,8 @@
 Finally, make sure that tests pass, as described in :ref:`basic-tests`.
 
 You can now proceed to :doc:`post-installation`.
+
+Common error messages
+---------------------
+
+Consult :doc:`common-error-messages` for solutions to a range of error messages seen during setup.
\ No newline at end of file


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 doc/test.rst
--- a/doc/test.rst
+++ b/doc/test.rst
@@ -99,66 +99,9 @@
 
    A common error when wanting to run tests against a particular database is to change ``sqlalchemy.url`` in ``test.ini`` or ``test-core.ini``. The problem is that these are versioned files and people have checked in these by mistake, creating problems for other developers and the CKAN buildbot. This is easily avoided by only changing ``sqlalchemy.url`` in your local ``development.ini`` and testing ``--with-pylons=test-core.ini``.
 
-Common problems running tests
------------------------------
+Common error messages
+---------------------
 
-* `nose.config.ConfigError: Error reading config file 'setup.cfg': no such option 'with-pylons'`
+Often errors are due to set-up errors. Always refer to the CKAN buildbot as the canonical build.
 
-   This error can result when you run nosetests for two reasons:
-
-   1. Pylons nose plugin failed to run. If this is the case, then within a couple of lines of running `nosetests` you'll see this warning: `Unable to load plugin pylons` followed by an error message. Fix the error here first.
-
-   2. The Python module 'Pylons' is not installed into you Python environment. Confirm this with::
-
-        python -c "import pylons"
-
-* `OperationalError: (OperationalError) no such function: plainto_tsquery ...`
-
-   This error usually results from running a test which involves search functionality, which requires using a PostgreSQL database, but another (such as SQLite) is configured. The particular test is either missing a `@search_related` decorator or there is a mixup with the test configuration files leading to the wrong database being used.
-
-* `ImportError: No module named worker`
-
-   The python entry point for the worker has not been generated. This occurs during the 'pip install' of the CKAN source, and needs to be done again if switching from older code that didn't have it. To recitify it::
-
-        python setup.py egg_info
-
-* `ImportError: cannot import name get_backend`
-
-   This can be caused by an out of date pyc file. Delete all your pyc files and start again::
-
-        find . -name "*.pyc" | xargs rm
-
-* `ImportError: cannot import name UnicodeMultiDict`
-
-   This is caused by using a version of WebOb that is too new (it has deprecated UnicodeMultiDict). Check the version like this (ensure you have activated your python environment first)::
-
-         pip freeze | grep -i webob
-
-   Now install the version specified in requires/lucid_present.txt. e.g.::
-
-         pip install webob==1.0.8
-
-* `nosetests: error: no such option: --ckan`
-
-   Nose is either unable to find ckan/ckan_nose_plugin.py in the python environment it is running in, or there is an error loading it. If there is an error, this will surface it::
-
-         nosetests --version
-
-   There are a few things to try to remedy this:
-
-   Commonly this is because the nosetests isn't running in the python environment. You need to have nose actually installed in the python environment. To see which you are running, do this::
-
-         which nosetests
-
-   If you have activated the environment and this still reports ``/usr/bin/nosetests`` then you need to::
-
-         pip install --ignore-installed nose
-
-   If ``nose --version`` still fails, ensure that ckan is installed in your environment::
-
-         cd pyenv/src/ckan
-         python setup.py develop
-
-   One final check - the version of nose should be at least 1.0. Check with::
-
-         pip freeze | grep -i nose
\ No newline at end of file
+Consult :doc:`common-error-messages` for solutions to a range of setup problems.
\ No newline at end of file


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 test-core.ini
--- a/test-core.ini
+++ b/test-core.ini
@@ -53,6 +53,7 @@
 test_smtp_server = localhost:6675
 ckan.mail_from = info at test.ckan.net
 
+ckan.locale_default = en
 
 # Logging configuration
 [loggers]



https://bitbucket.org/okfn/ckan/changeset/dfbc3cdbaf58/
changeset:   dfbc3cdbaf58
branch:      defect-1418-i18n
user:        dread
date:        2011-10-25 13:31:29
summary:     [controllers]: #1388 no need for hack for caching now etags fixed for language.
affected #:  1 file

diff -r a3c8d4f7cf91c075d2e77168f64b3cbdd4151989 -r dfbc3cdbaf58502100ecbcb56bd4bc0309ea64e9 ckan/controllers/home.py
--- a/ckan/controllers/home.py
+++ b/ckan/controllers/home.py
@@ -97,8 +97,6 @@
             # no need for error, just don't redirect
             return 
         return_to += '&' if '?' in return_to else '?'
-        # hack to prevent next page being cached
-        return_to += '__cache=%s' %  int(random.random()*100000000)
         redirect_to(return_to)
 
     def cache(self, id):



https://bitbucket.org/okfn/ckan/changeset/fd99da662378/
changeset:   fd99da662378
branch:      release-v1.5
user:        dread
date:        2011-10-25 13:47:53
summary:     [merge] defect-1418-i18n.
affected #:  18 files

diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 ckan/config/deployment.ini_tmpl
--- a/ckan/config/deployment.ini_tmpl
+++ b/ckan/config/deployment.ini_tmpl
@@ -179,6 +179,16 @@
 #ckan.recaptcha.publickey = 
 #ckan.recaptcha.privatekey = 
 
+# Locale/languages
+# TODO: Figure out a nicer way to get this. From the .ini? 
+# Order these by number of people speaking it in Europe:
+# http://en.wikipedia.org/wiki/Languages_of_the_European_Union#Knowledge
+# (or there abouts)
+ckan.locale_default = en
+#ckan.locales_offered = 
+ckan.locale_order = en de fr it es pl ru nl sv no cs_CZ hu pt_BR fi bg ca sq
+ckan.locales_filtered_out = el
+
 # Logging configuration
 [loggers]
 keys = root, ckan, ckanext


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 ckan/controllers/home.py
--- a/ckan/controllers/home.py
+++ b/ckan/controllers/home.py
@@ -2,13 +2,14 @@
 import sys
 
 from pylons import cache, config
+from pylons.i18n import set_lang
 from genshi.template import NewTextTemplate
 import sqlalchemy.exc
 
 from ckan.authz import Authorizer
 from ckan.logic import NotAuthorized
 from ckan.logic import check_access, get_action
-from ckan.i18n import set_session_locale, set_lang
+from ckan.lib.i18n import set_session_locale, get_lang
 from ckan.lib.search import query_for, QueryOptions, SearchError
 from ckan.lib.cache import proxy_cache, get_cache_expires
 from ckan.lib.base import *
@@ -41,16 +42,11 @@
             
 
     @staticmethod
-    def _home_cache_key(latest_revision_id=None):
-        '''Calculate the etag cache key for the home page. If you have
-        the latest revision id then supply it as a param.'''
-        if not latest_revision_id:
-            latest_revision_id = model.repo.youngest_revision().id
+    def _home_cache_key():
+        '''Calculate the etag cache key for the home page.'''
         user_name = c.user
-        if latest_revision_id:
-            cache_key = str(hash((latest_revision_id, user_name)))
-        else:
-            cache_key = 'fresh-install'
+        language = get_lang()
+        cache_key = str(hash((user_name, language)))
         return cache_key
 
     @proxy_cache(expires=cache_expires)
@@ -86,6 +82,11 @@
                 abort(400, _('Invalid language specified'))
             try:
                 set_lang(locale)
+                # NOTE: When translating this string, substitute the word
+                # 'English' for the language being translated into.
+                # We do it this way because some Babel locales don't contain
+                # a display_name!
+                # e.g. babel.Locale.parse('no').get_display_name() returns None
                 h.flash_notice(_("Language has been set to: English"))
             except:
                 h.flash_notice("Language has been set to: English")
@@ -96,8 +97,6 @@
             # no need for error, just don't redirect
             return 
         return_to += '&' if '?' in return_to else '?'
-        # hack to prevent next page being cached
-        return_to += '__cache=%s' %  int(random.random()*100000000)
         redirect_to(return_to)
 
     def cache(self, id):


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 ckan/controllers/package.py
--- a/ckan/controllers/package.py
+++ b/ckan/controllers/package.py
@@ -7,7 +7,7 @@
 from sqlalchemy.orm import eagerload_all
 import genshi
 from pylons import config, cache
-from pylons.i18n import get_lang, _
+from pylons.i18n import _
 from autoneg.accept import negotiate
 from babel.dates import format_date, format_datetime, format_time
 
@@ -25,6 +25,7 @@
 from ckan.logic import NotFound, NotAuthorized, ValidationError
 from ckan.logic import tuplize_dict, clean_dict, parse_params, flatten_to_string_key
 from ckan.lib.dictization import table_dictize
+from ckan.lib.i18n import get_lang
 import ckan.forms
 import ckan.authz
 import ckan.rating
@@ -179,7 +180,8 @@
     def _pkg_cache_key(pkg):
         # note: we need pkg.id in addition to pkg.revision.id because a
         # revision may have more than one package in it.
-        return str(hash((pkg.id, pkg.latest_related_revision.id, c.user, pkg.get_average_rating())))
+        language = get_lang()
+        return str(hash((pkg.id, pkg.latest_related_revision.id, c.user, pkg.get_average_rating(), language)))
 
     @proxy_cache()
     def read(self, id):


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 ckan/i18n/__init__.py
--- a/ckan/i18n/__init__.py
+++ b/ckan/i18n/__init__.py
@@ -1,65 +0,0 @@
-from pylons.i18n import _, add_fallback, get_lang, set_lang, gettext
-from babel import Locale
-
-
-# TODO: Figure out a nicer way to get this. From the .ini? 
-# Order these by number of people speaking it in Europe:
-# http://en.wikipedia.org/wiki/Languages_of_the_European_Union#Knowledge
-# (or there abouts)
-_KNOWN_LOCALES = ['en',
-                  'de',
-                  'fr',
-                  'it',
-                  'es',
-                  'pl',
-                  'ru',
-                  'nl',
-                  'sv', # Swedish
-                  'no',
-#                  'el', # Greek
-                  'cs_CZ',
-                  'hu',
-                  'pt_BR',
-                  'fi', 
-                  'bg',
-                  'ca',
-                  'sq', 
-                  ]
-
-def get_available_locales():
-    return map(Locale.parse, _KNOWN_LOCALES)
-
-def get_default_locale():
-    from pylons import config
-    return Locale.parse(config.get('ckan.locale')) or \
-            Locale.parse('en')
-
-def set_session_locale(locale):
-    if locale not in _KNOWN_LOCALES:
-        raise ValueError
-    from pylons import session
-    session['locale'] = locale
-    session.save()
-
-def handle_request(request, tmpl_context):
-    from pylons import session
-
-    tmpl_context.language = locale = None
-    if 'locale' in session:
-        locale = Locale.parse(session.get('locale'))
-    else:
-        requested = [l.replace('-', '_') for l in request.languages]
-        locale = Locale.parse(Locale.negotiate(_KNOWN_LOCALES, requested))
-
-    if locale is None:
-        locale = get_default_locale()
-    
-    options = [str(locale), locale.language, str(get_default_locale()),
-        get_default_locale().language]
-    for language in options:
-        try:
-            set_lang(language) 
-            tmpl_context.language = language
-        except: pass
-
-


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 ckan/i18n/ckan.pot
--- a/ckan/i18n/ckan.pot
+++ b/ckan/i18n/ckan.pot
@@ -214,6 +214,7 @@
 msgid "Invalid language specified"
 msgstr ""
 
+#. NOTE: Substitute 'English' for the language being translated into.
 #: ckan/controllers/home.py:89
 msgid "Language has been set to: English"
 msgstr ""


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 ckan/lib/base.py
--- a/ckan/lib/base.py
+++ b/ckan/lib/base.py
@@ -20,7 +20,7 @@
 
 import ckan
 from ckan import authz
-from ckan import i18n
+from ckan.lib import i18n
 import ckan.lib.helpers as h
 from ckan.plugins import PluginImplementations, IGenshiStreamFilter
 from ckan.lib.helpers import json


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 ckan/lib/helpers.py
--- a/ckan/lib/helpers.py
+++ b/ckan/lib/helpers.py
@@ -19,7 +19,7 @@
 from routes import url_for, redirect_to
 from alphabet_paginate import AlphaPage
 from lxml.html import fromstring
-from ckan.i18n import get_available_locales
+from i18n import get_available_locales
 
 
 


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 ckan/lib/i18n.py
--- /dev/null
+++ b/ckan/lib/i18n.py
@@ -0,0 +1,201 @@
+import os
+
+import pylons
+from pylons.i18n import _, add_fallback, set_lang, gettext, LanguageError
+from pylons.i18n.translation import _get_translator
+from babel import Locale, localedata
+from babel.core import LOCALE_ALIASES
+
+import ckan.i18n
+
+def singleton(cls):
+    instances = {}
+    def getinstance():
+        if cls not in instances:
+            instances[cls] = cls()
+        return instances[cls]
+    return getinstance
+
+i18n_path = os.path.dirname(ckan.i18n.__file__)
+
+ at singleton
+class Locales(object):
+    def __init__(self):
+        from pylons import config
+
+        # Get names of the locales
+        # (must be a better way than scanning for i18n directory?)
+        known_locales = ['en'] + [locale_name for locale_name in os.listdir(i18n_path) \
+                                  if localedata.exists(locale_name)]
+        self._locale_names, self._default_locale_name = self._work_out_locales(
+            known_locales, config)
+        self._locale_objects = map(Locale.parse, self._locale_names)
+        self._default_locale_object = Locale.parse(self._default_locale_name)
+
+        self._aliases = LOCALE_ALIASES
+        self._aliases['pt'] = 'pt_BR' # Default Portuguese language to
+                                     # Brazilian territory, since
+                                     # we don't have a Portuguese territory
+                                     # translation currently.
+
+    def _work_out_locales(self, known_locales, config_dict):
+        '''Work out the locale_names to offer to the user and the default locale.
+        All locales in this method are strings, not Locale objects.'''
+        # pass in a config_dict rather than ckan.config to make this testable
+
+        # Get default locale
+        assert not config_dict.get('lang'), \
+               '"lang" config option not supported - please use ckan.locale_default instead.'
+        default_locale = config_dict.get('ckan.locale_default') or \
+                         config_dict.get('ckan.locale') or \
+                         None # in this case, set it later on
+        if default_locale:
+            assert default_locale in known_locales
+
+        # Filter and reorder locales by config options
+        def get_locales_in_config_option(config_option):
+            locales_ = config_dict.get(config_option, '').split()
+            if locales_:
+                unknown_locales = set(locales_) - set(known_locales)
+                assert not unknown_locales, \
+                       'Bad config option %r - locales not found: %s' % \
+                       (config_option, unknown_locales)
+            return locales_
+        only_locales_offered = get_locales_in_config_option('ckan.locales_offered')
+        if only_locales_offered:
+            locales = only_locales_offered
+        else:
+            locales = known_locales
+            
+        def move_locale_to_start_of_list(locale_):
+            if locale_ not in locales:
+                raise ValueError('Cannot find locale "%s" in locales offered.' % locale_)
+            locales.pop(locales.index(locale_))
+            locales.insert(0, locale_)
+            
+        locales_filtered_out = get_locales_in_config_option('ckan.locales_filtered_out')
+        for locale in locales_filtered_out:
+            try:
+                locales.pop(locales.index(locale))
+            except ValueError, e:
+                raise ValueError('Could not filter out locale "%s" from offered locale list "%s": %s') % \
+                      (locale, locales, e)
+
+        locale_order = get_locales_in_config_option('ckan.locale_order')
+        if locale_order:
+            for locale in locale_order[::-1]:
+                # bring locale_name to the front
+                try:
+                    move_locale_to_start_of_list(locale)
+                except ValueError, e:
+                    raise ValueError('Could not process ckan.locale_order options "%s" for offered locale list "%s": %s' % \
+                                     (locale_order, locales, e))
+        elif default_locale:
+            if default_locale not in locales:
+                raise ValueError('Default locale "%s" is not amongst locales offered: %s' % \
+                                 (default_locale, locales))
+            # move the default locale to the start of the list
+            try:
+                move_locale_to_start_of_list(default_locale)
+            except ValueError, e:
+                raise ValueError('Could not move default locale "%s" to the start ofthe list of offered locales "%s": %s' % \
+                                 (default_locale, locales, e))
+
+        assert locales
+            
+        if not default_locale:
+            default_locale = locales[0]
+        assert default_locale in locales
+
+        return locales, default_locale
+
+    def get_available_locales(self):
+        '''Returns a list of the locale objects for which translations are
+        available.'''
+        return self._locale_objects
+
+    def get_available_locale_names(self):
+        '''Returns a list of the locale strings for which translations are
+        available.'''
+        return self._locale_names
+
+    def get_default_locale(self):
+        '''Returns the default locale/language as specified in the CKAN
+        config. It is a locale object.'''
+        return self._default_locale_object
+
+    def get_aliases(self):
+        '''Returns a mapping of language aliases, like the Babel LOCALE_ALIASES
+        but with hacks for specific CKAN issues.'''
+        return self._aliases
+
+    def negotiate_known_locale(self, preferred_locales):
+        '''Given a list of preferred locales, this method returns the best
+        match locale object from the known ones.'''
+        assert isinstance(preferred_locales, (tuple, list))
+        preferred_locales = [str(l).replace('-', '_') for l in preferred_locales]
+        return Locale.parse(Locale.negotiate(preferred_locales,
+                                             self.get_available_locale_names(),
+                                             aliases=self.get_aliases()
+                                             ))
+
+def get_available_locales():
+    return Locales().get_available_locales()
+
+def set_session_locale(locale):
+    if locale not in get_available_locales():
+        raise ValueError
+    from pylons import session
+    session['locale'] = locale
+    session.save()
+
+def handle_request(request, tmpl_context):
+    from pylons import session
+
+    # Work out what language to show the page in.
+    locales = [] # Locale objects. Ordered highest preference first.
+    tmpl_context.language = None
+    if session.get('locale'):
+        # First look for locale saved in the session (by home controller)
+        locales.append(Locale.parse(session.get('locale')))
+    else:
+        # Next try languages in the HTTP_ACCEPT_LANGUAGE header
+        locales.append(Locales().negotiate_known_locale(request.languages))
+
+    # Next try the default locale in the CKAN config file
+    locales.append(Locales().get_default_locale())
+
+    locale = set_lang_list(locales)
+    tmpl_context.language = locale.language
+    return locale
+
+def set_lang_list(locales):
+    '''Takes a list of locales (ordered by reducing preference) and tries
+    to set them in order. If one fails then it puts up a flash message and
+    tries the next.'''
+    import ckan.lib.helpers as h
+    failed_locales = set()
+    for locale in locales:
+        # try locales in order of preference until one works
+        try:
+            if str(locale) == 'en':
+                # There is no language file for English, so if we set_lang
+                # we would get an error. Just don't set_lang and finish.
+                break
+            set_lang(str(locale))
+            break
+        except LanguageError, e:
+            if str(locale) not in failed_locales:
+                h.flash_error('Could not change language to %r: %s' % \
+                              (str(locale), e))
+                failed_locales.add(str(locale))
+    return locale
+
+def get_lang():
+    '''Returns the current language. Based on babel.i18n.get_lang but works
+    when set_lang has not been run (i.e. still in English).'''
+    langs = pylons.i18n.get_lang()
+    if langs:
+        return langs[0]
+    else:
+        return 'en'


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 ckan/tests/functional/test_home.py
--- a/ckan/tests/functional/test_home.py
+++ b/ckan/tests/functional/test_home.py
@@ -1,4 +1,5 @@
-from pylons import c
+from pylons import c, session
+from pylons.i18n import set_lang
 
 from ckan.lib.create_test_data import CreateTestData
 from ckan.controllers.home import HomeController
@@ -21,11 +22,14 @@
     def teardown_class(self):
         model.repo.rebuild_db()
 
+    def clear_language_setting(self):
+        self.app.cookies = {}
+
     def test_home_page(self):
         offset = url_for('home')
         res = self.app.get(offset)
-        print res
         assert 'Add a dataset' in res
+        assert 'Could not change language' not in res
 
     def test_calculate_etag_hash(self):
         c.user = 'test user'
@@ -38,11 +42,11 @@
         hash_3 = get_hash()
         assert hash_2 != hash_3
 
-        model.repo.new_revision()
-        model.Session.add(model.Package(name=u'test_etag'))
-        model.repo.commit_and_remove()
-        hash_4 = get_hash()
-        assert hash_3 != hash_4
+        # I can't get set_lang to work and deliver correct
+        # result to get_lang, so leaving it commented
+##        set_lang('fr')
+##        hash_4 = get_hash()
+##        assert hash_3 != hash_4
 
     @search_related
     def test_packages_link(self):
@@ -63,6 +67,36 @@
         res = self.app.get(offset)
         assert '<strong>TEST TEMPLATE_FOOTER_END TEST</strong>'
 
+    def test_locale_detect(self):
+        offset = url_for('home')
+        self.clear_language_setting()
+        res = self.app.get(offset, headers={'Accept-Language': 'de,pt-br,en'})
+        try:
+            assert 'Willkommen' in res.body, res.body
+        finally:
+            self.clear_language_setting()
+
+    def test_locale_negotiate(self):
+        offset = url_for('home')
+        self.clear_language_setting()
+        res = self.app.get(offset, headers={'Accept-Language': 'fr-ca'})
+        # Request for French with Canadian territory should negotiate to
+        # just 'fr'
+        try:
+            assert 'propos' in res.body, res.body
+        finally:
+            self.clear_language_setting()
+
+    def test_locale_negotiate_pt(self):
+        offset = url_for('home')
+        self.clear_language_setting()
+        res = self.app.get(offset, headers={'Accept-Language': 'pt'})
+        # Request for Portuguese should find pt_BR because of our alias hack
+        try:
+            assert 'Bem-vindo' in res.body, res.body
+        finally:
+            self.clear_language_setting()
+
     def test_locale_change(self):
         offset = url_for('home')
         res = self.app.get(offset)
@@ -71,7 +105,7 @@
             res = res.follow()
             assert 'Willkommen' in res.body
         finally:
-            res = res.click('English')
+            self.clear_language_setting()
 
     def test_locale_change_invalid(self):
         offset = url_for(controller='home', action='locale', locale='')
@@ -106,9 +140,7 @@
             res = res.goto(href)
             assert res.status == 200, res.status # doesn't redirect
         finally:
-            offset = url_for('home')
-            res = self.app.get(offset)
-            res = res.click('English')
+            self.clear_language_setting()
 
 class TestDatabaseNotInitialised(TestController):
     @classmethod


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 ckan/tests/lib/test_i18n.py
--- /dev/null
+++ b/ckan/tests/lib/test_i18n.py
@@ -0,0 +1,154 @@
+from nose.tools import assert_equal, assert_raises
+from babel import Locale
+from pylons import config, session
+import pylons
+from pylons.i18n import get_lang
+
+from ckan.lib.i18n import Locales, set_session_locale, set_lang
+import ckan.lib.i18n
+
+from ckan.tests.pylons_controller import PylonsTestCase, TestSession
+
+class TestLocales:
+    def test_work_out_locales__thedatahub(self):
+        # as it is (roughly) on thedatahub.org
+        locales, default = Locales()._work_out_locales(
+            ['en', 'fr', 'de'],
+            {'ckan.locale': 'en'})
+        assert_equal(locales, ['en', 'fr', 'de'])
+        assert_equal(default, 'en')
+
+    def test_work_out_locales__france(self):
+        # as it is (roughly) on a foreign language site
+        locales, default = Locales()._work_out_locales(
+            ['en', 'fr', 'de'],
+            {'ckan.locale': 'fr'})
+        # fr moved to start of the list
+        assert_equal(locales, ['fr', 'en', 'de'])
+        assert_equal(default, 'fr')
+
+    def test_work_out_locales__locales_offered(self):
+        locales, default = Locales()._work_out_locales(
+            ['en', 'fr', 'de'],
+            {'ckan.locales_offered': 'fr de'})
+        assert_equal(locales, ['fr', 'de'])
+        assert_equal(default, 'fr')
+
+    def test_work_out_locales__locales_order(self):
+        locales, default = Locales()._work_out_locales(
+            ['en', 'fr', 'de'],
+            {'ckan.locale': 'fr',
+             'ckan.locale_order': 'de fr en'})
+        assert_equal(locales, ['de', 'fr', 'en'])
+        assert_equal(default, 'fr')
+
+    def test_work_out_locales__locales_filtered_out(self):
+        locales, default = Locales()._work_out_locales(
+            ['en', 'fr', 'de'],
+            {'ckan.locale': 'fr',
+             'ckan.locales_filtered_out': 'de'})
+        assert_equal(locales, ['fr', 'en'])
+        assert_equal(default, 'fr')
+        
+    def test_work_out_locales__default(self):
+        # don't specify default lang and it is not en,
+        # so default to next in list.
+        locales, default = Locales()._work_out_locales(
+            ['en', 'fr', 'de'],
+            {'ckan.locale': 'fr',
+             'ckan.locales_filtered_out': 'en'})
+        assert_equal(locales, ['fr', 'de'])
+        assert_equal(default, 'fr')
+
+    def test_work_out_locales__bad_default(self):
+        assert_raises(ValueError, Locales()._work_out_locales, 
+            ['en', 'fr', 'de'],
+            {'ckan.locale': 'en',
+             'ckan.locales_offered': 'fr de'})
+
+    def test_get_available_locales(self):
+        locales = Locales().get_available_locales()
+        assert len(locales) > 5, locales
+        locale = locales[0]
+        assert isinstance(locale, Locale)
+
+        locales_str = set([str(locale) for locale in locales])
+        langs = set([locale.language for locale in locales])
+        assert set(('en', 'de', 'cs_CZ')) < locales_str, locales_str
+        assert set(('en', 'de', 'cs')) < langs, langs
+
+    def test_default_locale(self):
+        # This should be setup in test-core.ini
+        assert_equal(config.get('ckan.locale_default'), 'en')
+        default_locale = Locales().get_default_locale()
+        assert isinstance(default_locale, Locale)
+        assert_equal(default_locale.language, 'en')
+
+    def test_negotiate_known_locale(self):
+        # check exact matches always work
+        locales = Locales().get_available_locales()
+        for locale in locales:
+            result = Locales().negotiate_known_locale([locale])
+            assert_equal(result, locale)
+
+        assert_equal(Locales().negotiate_known_locale(['en_US']), 'en')
+        assert_equal(Locales().negotiate_known_locale(['en_AU']), 'en')
+        assert_equal(Locales().negotiate_known_locale(['es_ES']), 'es')
+        assert_equal(Locales().negotiate_known_locale(['pt']), 'pt_BR')
+
+class TestI18n(PylonsTestCase):
+    def test_set_session_locale(self):
+        set_session_locale('en')
+        assert_equal(session['locale'], 'en')
+
+        set_session_locale('fr')
+        assert_equal(session['locale'], 'fr')
+
+    def handle_request(self, session_language=None, languages_header=[]):
+        session['locale'] = session_language
+        class FakePylons:
+            translator = None
+        class FakeRequest:
+            # Populated from the HTTP_ACCEPT_LANGUAGE header normally
+            languages = languages_header
+            # Stores details of the translator
+            environ = {'pylons.pylons': FakePylons()}
+        request = FakeRequest()
+        real_pylons_request = pylons.request
+        try:
+            pylons.request = request # for set_lang to work
+            class FakeTmplContext:
+                language = None # gets filled in by handle_request
+            tmpl_context = FakeTmplContext()
+            ckan.lib.i18n.handle_request(request, tmpl_context)
+            return tmpl_context.language # the language that got set
+        finally:
+            pylons.request = real_pylons_request
+    
+    def test_handle_request__default(self):
+        assert_equal(self.handle_request(),
+                     'en')
+        
+    def test_handle_request__session(self):
+        assert_equal(self.handle_request(session_language='fr'),
+                     'fr')
+
+    def test_handle_request__header(self):
+        assert_equal(self.handle_request(languages_header=['de']),
+                     'de')
+
+    def test_handle_request__header_negotiate(self):
+        # Language so is not an option, so reverts to next one
+        assert_equal(self.handle_request(languages_header=['so_KE', 'de']),
+                     'de')
+
+    def test_handle_request__header_but_defaults(self):
+        # Language so is not an option, so reverts to default
+        assert_equal(self.handle_request(languages_header=['so_KE']),
+                     'en')
+
+    def test_handle_request__header_territory(self):
+        # Request for specific version of German ends up simply as de.
+        assert_equal(self.handle_request(languages_header=['fr_CA', 'en']),
+                     'fr')
+        


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 doc/api.rst
--- a/doc/api.rst
+++ b/doc/api.rst
@@ -556,4 +556,4 @@
 Action API
 ~~~~~~~~~~
 
-See: 
\ No newline at end of file
+See: :doc:`apiv3`
\ No newline at end of file


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 doc/common-error-messages.rst
--- /dev/null
+++ b/doc/common-error-messages.rst
@@ -0,0 +1,75 @@
+Common error messages
+---------------------
+
+Whether a developer runs CKAN using paster or going through CKAN test suite, there are a number of error messages seen that are the result of setup problems. As people experience them, please add them to the list here.
+
+``nose.config.ConfigError: Error reading config file 'setup.cfg': no such option 'with-pylons'``
+================================================================================================
+
+   This error can result when you run nosetests for two reasons:
+
+   1. Pylons nose plugin failed to run. If this is the case, then within a couple of lines of running `nosetests` you'll see this warning: `Unable to load plugin pylons` followed by an error message. Fix the error here first.
+
+   2. The Python module 'Pylons' is not installed into you Python environment. Confirm this with::
+
+        python -c "import pylons"
+
+``OperationalError: (OperationalError) no such function: plainto_tsquery ...``
+==============================================================================
+
+   This error usually results from running a test which involves search functionality, which requires using a PostgreSQL database, but another (such as SQLite) is configured. The particular test is either missing a `@search_related` decorator or there is a mixup with the test configuration files leading to the wrong database being used.
+
+``ImportError: No module named worker``
+=======================================
+
+   The python entry point for the worker has not been generated. This occurs during the 'pip install' of the CKAN source, and needs to be done again if switching from older code that didn't have it. To recitify it::
+
+        python setup.py egg_info
+
+``ImportError: cannot import name get_backend``
+===============================================
+
+   This can be caused by an out of date pyc file. Delete all your pyc files and start again::
+
+        find . -name "*.pyc" | xargs rm
+
+``ImportError: cannot import name UnicodeMultiDict``
+====================================================
+
+   This is caused by using a version of WebOb that is too new (it has deprecated UnicodeMultiDict). Check the version like this (ensure you have activated your python environment first)::
+
+         pip freeze | grep -i webob
+
+   Now install the version specified in requires/lucid_present.txt. e.g.::
+
+         pip install webob==1.0.8
+
+``nosetests: error: no such option: --ckan``
+============================================
+
+   Nose is either unable to find ckan/ckan_nose_plugin.py in the python environment it is running in, or there is an error loading it. If there is an error, this will surface it::
+
+         nosetests --version
+
+   There are a few things to try to remedy this:
+
+   Commonly this is because the nosetests isn't running in the python environment. You need to have nose actually installed in the python environment. To see which you are running, do this::
+
+         which nosetests
+
+   If you have activated the environment and this still reports ``/usr/bin/nosetests`` then you need to::
+
+         pip install --ignore-installed nose
+
+   If ``nose --version`` still fails, ensure that ckan is installed in your environment::
+
+         cd pyenv/src/ckan
+         python setup.py develop
+
+   One final check - the version of nose should be at least 1.0. Check with::
+
+         pip freeze | grep -i nose
+
+``AttributeError: 'unicode' object has no attribute 'items'`` (Cookie.py)
+=========================================================================
+


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 doc/configuration.rst
--- a/doc/configuration.rst
+++ b/doc/configuration.rst
@@ -194,18 +194,54 @@
 -----------------------------
 
 .. index::
-   single: lang
+   single: ckan.locale_default
 
-lang
-^^^^
+ckan.locale_default
+^^^^^^^^^^^^^^^^^^^
 
 Example::
 
- lang=de
+ ckan.locale_default=de
 
 Default value:  ``en`` (English)
 
-Use this to specify the language of the text displayed in the CKAN web UI. This requires a suitable `mo` file installed for the language. For more information on internationalization, see :doc:`i18n`.
+Use this to specify the locale (language of the text) displayed in the CKAN Web UI. This requires a suitable `mo` file installed for the locale in the ckan/i18n. For more information on internationalization, see :doc:`i18n`. If you don't specify a default locale, then it will default to the first locale offered, which is by default English (alter that with `ckan.locales_offered` and `ckan.locales_filtered_out`.
+
+.. note: In versions of CKAN before 1.5, the settings used for this was variously `lang` or `ckan.locale`, which have now been deprecated in favour of `ckan.locale_default`.
+
+ckan.locales_offered
+^^^^^^^^^^^^^^^^^^^^
+
+Example::
+
+ ckan.locales_offered=en de fr
+
+Default value: (none)
+
+By default, all locales found in the ckan/i18n directory will be offered to the user. To only offer a subset of these, list them under this option. The ordering of the locales is preserved when offered to the user.
+
+ckan.locales_filtered_out
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Example::
+
+ ckan.locales_filtered_out=pl ru
+
+Default value: (none)
+
+If you want to not offer particular locales to the user, then list them here to have them removed from the options.
+
+ckan.locale_order
+^^^^^^^^^^^^^^^^^
+
+Example::
+
+ ckan.locale_order=fr de
+
+Default value: (none)
+
+If you want to specify the ordering of all or some of the locales as they are offered to the user, then specify them here in the required order. Any locales that are available but not specified in this option, will still be offered at the end of the list.
+
 
 Theming Settings
 ----------------


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 doc/i18n.rst
--- a/doc/i18n.rst
+++ b/doc/i18n.rst
@@ -2,16 +2,18 @@
 Internationalize CKAN
 =====================
 
-CKAN is used in many countries, and adding a new language is a simple process. 
+CKAN is used in many countries, and adding a new language to the web interface is a simple process. 
+
+.. Note: Storing metadata field values in more than one language is a separate topic. This is achieved by storing the translations in extra fields. A custom dataset form and dataset display template are recommended. Ask the CKAN team for more information.
 
 Supported Languages
 ===================
 
 CKAN already supports numerous languages. To check whether your language is supported, look in the source at ``ckan/i18n`` for translation files. Languages are named using two-letter ISO language codes (e.g. ``es``, ``de``).
 
-If your language is present, you can switch language simply by setting the ``lang`` option in your CKAN config file, as described in :ref:`config-i18n`. For example, to switch to German::
+If your language is present, you can switch the default language simply by setting the ``ckan.locale_default`` option in your CKAN config file, as described in :ref:`config-i18n`. For example, to switch to German::
 
- lang=de
+ ckan.locale_default=de
 
 If your language is not supported yet, the remainder of this section section provides instructions on how to prepare a translation file and add it to CKAN. 
 
@@ -20,8 +22,36 @@
 
 If you want to add an entirely new language to CKAN, you have two options.
 
-* :ref:`i18n-manual`. Creating translation files manually.  
 * :ref:`i18n-transifex`. Creating translation files using Transifex, the open source translation software. 
+* :ref:`i18n-manual`. Creating translation files manually.
+
+
+.. _i18n-transifex:
+
+Transifex Setup
+---------------
+
+Transifex, the open translation platform, provides a simple web interface for writing translations and is widely used for CKAN internationalization. 
+
+Using Transifex makes it easier to handle collaboration, with an online editor that makes the process more accessible.
+
+Existing CKAN translation projects can be found at: https://www.transifex.net/projects/p/ckan/teams/
+
+Updated translations are automatically pushed to https://bitbucket.org/bboissin/ckan-i18n and these can be compiled and placed on CKAN servers by the server administrators.
+
+Transifex Administration
+++++++++++++++++++++++++
+
+The Transifex workflow is as follows:
+
+* Install transifex command-line utilities
+* ``tx init`` in CKAN to connect to Transifex
+* Run ``python setup.py extract_messages`` on the CKAN source
+* Upload the local .pot file via command-line ``tx push``
+* Get people to complete translations on Transifex
+* Pull locale .po files via ``tx pull``
+* ``python setup.py compile_catalog``
+* Mercurial Commit and push po and mo files
 
 
 .. _i18n-manual:
@@ -29,7 +59,9 @@
 Manual Setup
 ------------
 
-If you prefer not to use Transifex, you can create translation files manually.
+If you prefer not to use Transifex, you can create translation files manually. 
+
+.. note:: Please keep the CKAN core developers aware of new languages created in this way.
 
 All the English strings in CKAN are extracted into the ``ckan.pot`` file, which can be found in ``ckan/i18n``.
 
@@ -112,32 +144,5 @@
 6. Configure the Language
 +++++++++++++++++++++++++
 
-Finally, once the mo file is in place, you can switch between the installed languages using the ``lang`` option in the CKAN config file, as described in :ref:`config-i18n`. 
+Finally, once the mo file is in place, you can switch between the installed languages using the ``ckan.locale`` option in the CKAN config file, as described in :ref:`config-i18n`. 
 
-
-.. _i18n-transifex:
-
-Transifex Setup
----------------
-
-Transifxes, the open translation platform, provides a simple web interface for writing translations and is widely used for CKAN internationalization. 
-
-Using Transifex makes it easier to handle collaboration, with an online editor that makes the process more accessible.
-
-Existing CKAN translation projects can be found at: https://www.transifex.net/projects/p/ckan/teams/
-
-Updated translations are automatically pushed to https://bitbucket.org/bboissin/ckan-i18n and these can be compiled and placed on CKAN servers by the server administrators.
-
-Transifex Administration
-++++++++++++++++++++++++
-
-The Transifex workflow is as follows:
-
-* Install transifex command-line utilities
-* ``tx init`` in CKAN to connect to Transifex
-* Run ``python setup.py extract_messages`` on the CKAN source
-* Upload the local .pot file via command-line ``tx push``
-* Get people to complete translations on Transifex
-* Pull locale .po files via ``tx pull``
-* ``python setup.py compile_catalog``
-* Commit and push po and mo files


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 doc/index.rst
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -33,6 +33,7 @@
    configuration
    api
    test
+   common-error-messages
    buildbot
    about
 


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 doc/install-from-source.rst
--- a/doc/install-from-source.rst
+++ b/doc/install-from-source.rst
@@ -295,3 +295,8 @@
 Finally, make sure that tests pass, as described in :ref:`basic-tests`.
 
 You can now proceed to :doc:`post-installation`.
+
+Common error messages
+---------------------
+
+Consult :doc:`common-error-messages` for solutions to a range of error messages seen during setup.
\ No newline at end of file


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 doc/test.rst
--- a/doc/test.rst
+++ b/doc/test.rst
@@ -99,66 +99,9 @@
 
    A common error when wanting to run tests against a particular database is to change ``sqlalchemy.url`` in ``test.ini`` or ``test-core.ini``. The problem is that these are versioned files and people have checked in these by mistake, creating problems for other developers and the CKAN buildbot. This is easily avoided by only changing ``sqlalchemy.url`` in your local ``development.ini`` and testing ``--with-pylons=test-core.ini``.
 
-Common problems running tests
------------------------------
+Common error messages
+---------------------
 
-* `nose.config.ConfigError: Error reading config file 'setup.cfg': no such option 'with-pylons'`
+Often errors are due to set-up errors. Always refer to the CKAN buildbot as the canonical build.
 
-   This error can result when you run nosetests for two reasons:
-
-   1. Pylons nose plugin failed to run. If this is the case, then within a couple of lines of running `nosetests` you'll see this warning: `Unable to load plugin pylons` followed by an error message. Fix the error here first.
-
-   2. The Python module 'Pylons' is not installed into you Python environment. Confirm this with::
-
-        python -c "import pylons"
-
-* `OperationalError: (OperationalError) no such function: plainto_tsquery ...`
-
-   This error usually results from running a test which involves search functionality, which requires using a PostgreSQL database, but another (such as SQLite) is configured. The particular test is either missing a `@search_related` decorator or there is a mixup with the test configuration files leading to the wrong database being used.
-
-* `ImportError: No module named worker`
-
-   The python entry point for the worker has not been generated. This occurs during the 'pip install' of the CKAN source, and needs to be done again if switching from older code that didn't have it. To recitify it::
-
-        python setup.py egg_info
-
-* `ImportError: cannot import name get_backend`
-
-   This can be caused by an out of date pyc file. Delete all your pyc files and start again::
-
-        find . -name "*.pyc" | xargs rm
-
-* `ImportError: cannot import name UnicodeMultiDict`
-
-   This is caused by using a version of WebOb that is too new (it has deprecated UnicodeMultiDict). Check the version like this (ensure you have activated your python environment first)::
-
-         pip freeze | grep -i webob
-
-   Now install the version specified in requires/lucid_present.txt. e.g.::
-
-         pip install webob==1.0.8
-
-* `nosetests: error: no such option: --ckan`
-
-   Nose is either unable to find ckan/ckan_nose_plugin.py in the python environment it is running in, or there is an error loading it. If there is an error, this will surface it::
-
-         nosetests --version
-
-   There are a few things to try to remedy this:
-
-   Commonly this is because the nosetests isn't running in the python environment. You need to have nose actually installed in the python environment. To see which you are running, do this::
-
-         which nosetests
-
-   If you have activated the environment and this still reports ``/usr/bin/nosetests`` then you need to::
-
-         pip install --ignore-installed nose
-
-   If ``nose --version`` still fails, ensure that ckan is installed in your environment::
-
-         cd pyenv/src/ckan
-         python setup.py develop
-
-   One final check - the version of nose should be at least 1.0. Check with::
-
-         pip freeze | grep -i nose
\ No newline at end of file
+Consult :doc:`common-error-messages` for solutions to a range of setup problems.
\ No newline at end of file


diff -r 81dcbf85a2fa4202ce88cb365bcd85554c18b981 -r fd99da66237857f9fa6a14d9023dda1b2a39eef4 test-core.ini
--- a/test-core.ini
+++ b/test-core.ini
@@ -53,6 +53,7 @@
 test_smtp_server = localhost:6675
 ckan.mail_from = info at test.ckan.net
 
+ckan.locale_default = en
 
 # Logging configuration
 [loggers]

Repository URL: https://bitbucket.org/okfn/ckan/

--

This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.




More information about the ckan-changes mailing list