Skip to content

Commit

Permalink
[WIP] check usable languages
Browse files Browse the repository at this point in the history
  • Loading branch information
cfm committed Apr 7, 2022
1 parent 27f17b1 commit 635b856
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 31 deletions.
41 changes: 21 additions & 20 deletions securedrop/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@
from flask import Flask, g, request, session
from flask_babel import Babel

from sdconfig import SDConfig

from sdconfig import SDConfig, FALLBACK_LOCALE

class RequestLocaleInfo:
"""
Expand Down Expand Up @@ -128,28 +127,30 @@ def configure_babel(config: SDConfig, app: Flask) -> Babel:

def validate_locale_configuration(config: SDConfig, babel: Babel) -> None:
"""
Ensure that the configured locales are valid and translated.
Check that configured locales are available in the filesystem and therefore usable by
Babel. Warn about configured locales that are not usable, unless we're left with
no usable default or fallback locale, in which case raise an exception.
"""
if config.DEFAULT_LOCALE not in config.SUPPORTED_LOCALES:
raise ValueError(
'The default locale "{}" is not included in the set of supported locales "{}"'.format(
config.DEFAULT_LOCALE, config.SUPPORTED_LOCALES
)
)

translations = babel.list_translations()
for locale in config.SUPPORTED_LOCALES:
if locale == "en_US":
continue

available = set(babel.list_translations()) # available in the filesystem
available.add(FALLBACK_LOCALE)
configured = set(config.SUPPORTED_LOCALES) # configured by the administrator
usable = available & configured # usable by Babel

# This loop could be replaced with a set operation, but we'd have to iterate over the result
# anyway in order to log it.
for locale in configured:
parsed = Locale.parse(locale)
if parsed not in translations:
raise ValueError(
'Configured locale "{}" is not in the set of translated locales "{}"'.format(
parsed, translations
)
if parsed not in usable:
babel.app.logger.error(
f'Configured locale "{parsed}" is not in the set of usable locales {usable}'
)

defaults = set([config.DEFAULT_LOCALE, FALLBACK_LOCALE]) & usable
if len(defaults) == 0:
raise ValueError(
f'None of the default locales {defaults} is in the set of usable locales {usable}'
)


LOCALES = collections.OrderedDict() # type: collections.OrderedDict[str, RequestLocaleInfo]

Expand Down
5 changes: 4 additions & 1 deletion securedrop/sdconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
from typing import Set


FALLBACK_LOCALE = "en_US"


class SDConfig:
def __init__(self) -> None:
try:
Expand Down Expand Up @@ -120,7 +123,7 @@ def __init__(self) -> None:
# Config entries used by i18n.py
# Use en_US as the default locale if the key is not defined in _config
self.DEFAULT_LOCALE = getattr(
_config, "DEFAULT_LOCALE", "en_US"
_config, "DEFAULT_LOCALE", FALLBACK_LOCALE,
) # type: str
supported_locales = set(getattr(
_config, "SUPPORTED_LOCALES", [self.DEFAULT_LOCALE]
Expand Down
57 changes: 47 additions & 10 deletions securedrop/tests/test_i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,43 @@ def test_i18n(journalist_app, config):
verify_i18n(app)


def test_supported_locales(config):
def test_no_usable_default_locale(config):
'''
The apps fail if neither the default nor the fallback locale is usable.
'''
fake_config = SDConfig()
fake_config.DEFAULT_LOCALE = 'eo' # Esperanto
fake_config.SUPPORTED_LOCALES = ['eo']
fake_config.TRANSLATION_DIRS = Path(config.TEMP_DIR)

with pytest.raises(ValueError, match='in the set of usable locales'):
journalist_app_module.create_app(fake_config)

with pytest.raises(ValueError, match='in the set of usable locales'):
source_app.create_app(fake_config)


def test_unusable_default_but_usable_fallback_locale(config, caplog):
'''
The apps start even if the default locale is unusable, as along as the fallback locale is
usable, but log an error for OSSEC to pick up.
'''
fake_config = SDConfig()
fake_config.DEFAULT_LOCALE = 'eo' # Esperanto
fake_config.SUPPORTED_LOCALES = ['eo', 'en_US']
fake_config.TRANSLATION_DIRS = Path(config.TEMP_DIR)

for app in (journalist_app_module.create_app(fake_config),
source_app.create_app(fake_config)):
with app.app_context():
assert 'Configured locale "eo" is not in the set of usable locales' in caplog.text


# Check that an invalid locale raises an error during app
# configuration.
def test_invalid_locales(config):
'''
An invalid locale raises an error during app configuration.
'''
fake_config = SDConfig()
fake_config.SUPPORTED_LOCALES = ['en_US', 'yy_ZZ']
fake_config.TRANSLATION_DIRS = Path(config.TEMP_DIR)

Expand All @@ -239,16 +271,21 @@ def test_supported_locales(config):
with pytest.raises(UnknownLocaleError):
source_app.create_app(fake_config)

# Check that a valid but unsupported locale raises an error during
# app configuration.

def test_valid_but_unusable_locales(config, caplog):
'''
The apps start with one or more unusable, but still valid, locales, but log an error for
OSSEC to pick up.
'''
fake_config = SDConfig()

fake_config.SUPPORTED_LOCALES = ['en_US', 'wae_CH']
fake_config.TRANSLATION_DIRS = Path(config.TEMP_DIR)

with pytest.raises(ValueError, match="not in the set of translated locales"):
journalist_app_module.create_app(fake_config)

with pytest.raises(ValueError, match="not in the set of translated locales"):
source_app.create_app(fake_config)
for app in (journalist_app_module.create_app(fake_config),
source_app.create_app(fake_config)):
with app.app_context():
assert 'Configured locale "wae_CH" is not in the set of usable locales' in caplog.text


def test_language_tags():
Expand Down

0 comments on commit 635b856

Please sign in to comment.