Repository: wagtail/wagtail

Issue:
New project fails with USE_I18N=False
### Issue Summary

Initial migrations fail on Wagtail 2.11.1 with USE_I18N = False and LANGUAGE_CODE = 'en-us' in settings.

### Steps to Reproduce

1. Start a new project with `wagtail start myproject`
2. Edit `myproject/settings/base.py` and set `USE_I18N = False`
3. Run `./manage.py migrate`

This throws the exception:
```
Traceback (most recent call last):
  File "./manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/Users/matthew/.virtualenvs/use18n/lib/python3.7/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
    utility.execute()
  File "/Users/matthew/.virtualenvs/use18n/lib/python3.7/site-packages/django/core/management/__init__.py", line 395, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/Users/matthew/.virtualenvs/use18n/lib/python3.7/site-packages/django/core/management/base.py", line 330, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/Users/matthew/.virtualenvs/use18n/lib/python3.7/site-packages/django/core/management/base.py", line 371, in execute
    output = self.handle(*args, **options)
  File "/Users/matthew/.virtualenvs/use18n/lib/python3.7/site-packages/django/core/management/base.py", line 85, in wrapped
    res = handle_func(*args, **kwargs)
  File "/Users/matthew/.virtualenvs/use18n/lib/python3.7/site-packages/django/core/management/commands/migrate.py", line 92, in handle
    executor = MigrationExecutor(connection, self.migration_progress_callback)
  File "/Users/matthew/.virtualenvs/use18n/lib/python3.7/site-packages/django/db/migrations/executor.py", line 18, in __init__
    self.loader = MigrationLoader(self.connection)
  File "/Users/matthew/.virtualenvs/use18n/lib/python3.7/site-packages/django/db/migrations/loader.py", line 53, in __init__
    self.build_graph()
  File "/Users/matthew/.virtualenvs/use18n/lib/python3.7/site-packages/django/db/migrations/loader.py", line 210, in build_graph
    self.load_disk()
  File "/Users/matthew/.virtualenvs/use18n/lib/python3.7/site-packages/django/db/migrations/loader.py", line 112, in load_disk
    migration_module = import_module(migration_path)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 728, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/Users/matthew/.virtualenvs/use18n/lib/python3.7/site-packages/wagtail/core/migrations/0056_page_locale_fields_populate.py", line 8, in <module>
    class Migration(migrations.Migration):
  File "/Users/matthew/.virtualenvs/use18n/lib/python3.7/site-packages/wagtail/core/migrations/0056_page_locale_fields_populate.py", line 15, in Migration
    BootstrapTranslatableModel('wagtailcore.Page'),
  File "/Users/matthew/.virtualenvs/use18n/lib/python3.7/site-packages/wagtail/core/models.py", line 538, in __init__
    language_code = get_supported_content_language_variant(settings.LANGUAGE_CODE)
  File "/Users/matthew/.virtualenvs/use18n/lib/python3.7/site-packages/wagtail/core/utils.py", line 261, in get_supported_content_language_variant
    supported_lang_codes = get_content_languages()
  File "/Users/matthew/.virtualenvs/use18n/lib/python3.7/site-packages/wagtail/core/utils.py", line 224, in get_content_languages
    (default_language_code, languages[default_language_code]),
KeyError: 'en-us'
```

This seems to happen because with USE_I18N = False, the implementation of `get_supported_language_variant` in Django's 'null' i18n backend returns LANGUAGE_CODE unchanged, whereas the real one takes fallbacks into account (returning 'en' for 'en-us'). Wagtail's `get_content_languages` then expects to find this in settings.LANGUAGES, which fails because [Django's master LANGUAGES list](https://github.com/django/django/blob/stable/3.1.x/django/conf/global_settings.py) contains 'en', not 'en-us'.

* I have confirmed that this issue can be reproduced as described on a fresh Wagtail project: yes


### Technical details

* Python version: 3.7.2
* Django version: 3.1.3
* Wagtail version: 2.11.1



Discussion:

Resolving patch:
diff --git a/wagtail/core/utils.py b/wagtail/core/utils.py
--- a/wagtail/core/utils.py
+++ b/wagtail/core/utils.py
@@ -220,8 +220,23 @@ def get_content_languages():
     if content_languages is None:
         # Default to a single language based on LANGUAGE_CODE
         default_language_code = get_supported_language_variant(settings.LANGUAGE_CODE)
+        try:
+            language_name = languages[default_language_code]
+        except KeyError:
+            # get_supported_language_variant on the 'null' translation backend (used for
+            # USE_I18N=False) returns settings.LANGUAGE_CODE unchanged without accounting for
+            # language variants (en-us versus en), so retry with the generic version.
+            default_language_code = default_language_code.split("-")[0]
+            try:
+                language_name = languages[default_language_code]
+            except KeyError:
+                # Can't extract a display name, so fall back on displaying LANGUAGE_CODE instead
+                language_name = settings.LANGUAGE_CODE
+                # Also need to tweak the languages dict to get around the check below
+                languages[default_language_code] = settings.LANGUAGE_CODE
+
         content_languages = [
-            (default_language_code, languages[default_language_code]),
+            (default_language_code, language_name),
         ]
 
     # Check that each content language is in LANGUAGES


Test patch:
diff --git a/wagtail/core/tests/test_utils.py b/wagtail/core/tests/test_utils.py
--- a/wagtail/core/tests/test_utils.py
+++ b/wagtail/core/tests/test_utils.py
@@ -2,6 +2,7 @@
 from django.core.exceptions import ImproperlyConfigured
 from django.test import TestCase, override_settings
 from django.utils.text import slugify
+from django.utils.translation import _trans
 
 from wagtail.core.models import Page
 from wagtail.core.utils import (
@@ -259,3 +260,36 @@ def test_uses_wagtail_content_languages(self):
             g('xyz')
         with self.assertRaises(LookupError):
             g('xy-zz')
+
+
+@override_settings(
+    USE_I18N=False,
+    WAGTAIL_I18N_ENABLED=False,
+    WAGTAIL_CONTENT_LANGUAGES=None,
+    LANGUAGE_CODE='en-us',
+)
+class TestGetSupportedContentLanguageVariantWithI18nFalse(TestCase):
+    def setUp(self):
+        # Need to forcibly clear the django.utils.translation._trans object when overriding
+        # USE_I18N:
+        # https://github.com/django/django/blob/3.1/django/utils/translation/__init__.py#L46-L48
+        _trans.__dict__.clear()
+
+    def tearDown(self):
+        _trans.__dict__.clear()
+
+    def test_lookup_language_with_i18n_false(self):
+        # Make sure we can handle the 'null' USE_I18N=False implementation of
+        # get_supported_language_variant returning 'en-us' rather than 'en',
+        # despite 'en-us' not being in LANGUAGES.
+        # https://github.com/wagtail/wagtail/issues/6539
+
+        self.assertEqual(get_supported_content_language_variant('en-us'), 'en')
+
+    @override_settings(LANGUAGE_CODE='zz')
+    def test_language_code_not_in_languages(self):
+        # Ensure we can handle a LANGUAGE_CODE setting that isn't defined in LANGUAGES -
+        # in this case get_content_languages has to cope with not being able to retrieve
+        # a display name for the language
+        self.assertEqual(get_supported_content_language_variant('zz'), 'zz')
+        self.assertEqual(get_supported_content_language_variant('zz-gb'), 'zz')