Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code refactoring to get translated utilities at the same place #4590

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions hub/migrations/0013_alter_constance_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ def alter_constance_config(apps, schema_editor):
user_metadata = to_python_object(config.USER_METADATA_FIELDS)
name_set = False
for field in user_metadata:
if field['name'] == 'full_name':
if field['name'] == 'name':
name_set = True
break
if not name_set:
user_metadata.insert(0, {
'name': 'full_name',
'name': 'name',
'required': False,
})
user_metadata_json = LazyJSONSerializable(user_metadata)
Expand Down
Empty file added hub/tests/__init__.py
Empty file.
177 changes: 111 additions & 66 deletions hub/tests/test_i18n.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# coding: utf-8
import mock
from constance.test import override_config
from django.test import TestCase

from hub.utils.i18n import I18nUtils
from kobo.static_lists import (
PROJECT_METADATA_DEFAULT_LABELS,
USER_METADATA_DEFAULT_LABELS,
)
from kpi.utils.json import LazyJSONSerializable


class I18nTestCase(TestCase):
Expand All @@ -15,85 +18,127 @@ def setUp(self):
pass

def test_welcome_message(self):
welcome_message_fr = I18nUtils.get_sitewide_message(lang="fr")
welcome_message_es = I18nUtils.get_sitewide_message(lang="es")
welcome_message_fr = I18nUtils.get_sitewide_message(lang='fr')
welcome_message_es = I18nUtils.get_sitewide_message(lang='es')
welcome_message = I18nUtils.get_sitewide_message()

self.assertEqual(welcome_message_fr, "Le message de bienvenue")
self.assertEqual(welcome_message, "Global welcome message")
self.assertEqual(welcome_message_fr, 'Le message de bienvenue')
self.assertEqual(welcome_message, 'Global welcome message')
self.assertEqual(welcome_message_es, welcome_message)

def test_custom_label_translations(self):
def check_labels(field, default_labels, lang):
new_field = I18nUtils.set_custom_label(field, default_labels, lang)
assert new_field['name'] == field['name']
assert new_field['required'] == field['required']
assert new_field['label'] == field['label'][lang]

user_metadata_field = {
'name': 'Full name',
'required': False,
'label': {'default': 'Name', 'fr': 'Nom'},
# TODO validate whethere the tests below are necessary.
# Kinda redundant with kobo/apps/accounts/tests/test_forms.py::AccountFormsTestCase
@override_config(USER_METADATA_FIELDS=LazyJSONSerializable([
{
'name': 'name',
'required': False,
'label': {
'default': 'Full name',
'fr': 'Prénom et nom',
'es': 'Nombre y apellido'
}

project_metadata_field = {
'name': 'description',
'required': True,
'label': {'default': 'Description', 'fr': 'Details'},
}

check_labels(user_metadata_field, USER_METADATA_DEFAULT_LABELS, 'fr')
check_labels(
project_metadata_field, PROJECT_METADATA_DEFAULT_LABELS, 'fr'
}
]))
def test_user_metadata_fields_with_custom_label(self):
# Languages exist - return related label
assert (
I18nUtils.get_metadata_field_label('name', 'user', 'fr')
== 'Prénom et nom'
)
assert (
I18nUtils.get_metadata_field_label('name', 'user', 'es')
== 'Nombre y apellido'
)
# No matching languages - return default
assert (
I18nUtils.get_metadata_field_label('name', 'user', 'it')
== 'Full name'
)
assert I18nUtils.get_metadata_field_label('name', 'user') == 'Full name'

def test_metadata_no_label_field(self):
def check_labels(field, default_labels, lang):
new_field = I18nUtils.set_custom_label(field, default_labels, lang)
assert new_field['name'] == field['name']
assert new_field['required'] == field['required']
assert new_field['label'] == default_labels[field['name']]

user_metadata_field = {
'name': 'full_name',
'required': False,
@override_config(USER_METADATA_FIELDS=LazyJSONSerializable([
{
'name': 'name',
'required': False
}
]))
def test_user_metadata_fields_no_label_field(self):
MOCK_TRANSLATION_STRING = 'hello from gettext_lazy'
mock_t = mock.MagicMock(return_value=MOCK_TRANSLATION_STRING)

project_metadata_field = {
'name': 'description',
'required': True,
with mock.patch.dict(
USER_METADATA_DEFAULT_LABELS,
{'name': mock_t('My Full name')},
) as mock_dict:
assert (
I18nUtils.get_metadata_field_label('name', 'user', 'fr')
== MOCK_TRANSLATION_STRING
)
assert (
I18nUtils.get_metadata_field_label('name', 'user')
== MOCK_TRANSLATION_STRING
)
assert (
USER_METADATA_DEFAULT_LABELS['name']
== MOCK_TRANSLATION_STRING
)
assert mock_t.call_args.args[0] == 'My Full name'

@override_config(PROJECT_METADATA_FIELDS=LazyJSONSerializable([
{
'name': 'sector',
'required': False,
'label': {
'default': 'Activity sector',
'fr': 'Secteur d’activités',
'es': 'Sector de actividad'
}
}

check_labels(user_metadata_field, USER_METADATA_DEFAULT_LABELS, 'fr')
check_labels(
project_metadata_field, PROJECT_METADATA_DEFAULT_LABELS, 'fr'
]))
def test_project_metadata_fields_with_custom_label(self):
# Languages exist - return related label
assert (
I18nUtils.get_metadata_field_label('sector', 'project', 'fr')
== 'Secteur d’activités'
)
assert (
I18nUtils.get_metadata_field_label('sector', 'project', 'es')
== 'Sector de actividad'
)
# No matching languages - return default
assert (
I18nUtils.get_metadata_field_label('sector', 'project', 'it')
== 'Activity sector'
)
assert (
I18nUtils.get_metadata_field_label('sector', 'project')
== 'Activity sector'
)

def test_custom_label_no_lang(self):
def check_labels(field, default_labels, lang):
new_field = I18nUtils.set_custom_label(field, default_labels, lang)
assert new_field['name'] == field['name']
assert new_field['required'] == field['required']
assert new_field['label'] == field['label']['default']

user_metadata_field = {
'name': 'full_name',
@override_config(PROJECT_METADATA_FIELDS=LazyJSONSerializable([
{
'name': 'sector',
'required': False,
'label': {'default': 'Name'},
}
]))
def test_project_metadata_fields_no_label_field(self):
MOCK_TRANSLATION_STRING = 'hello from gettext_lazy'
mock_t = mock.MagicMock(return_value=MOCK_TRANSLATION_STRING)

project_metadata_field = {
'name': 'description',
'required': True,
'label': {'default': 'Description'},
}
check_labels(
user_metadata_field,
USER_METADATA_DEFAULT_LABELS,
None
)
check_labels(
project_metadata_field,
with mock.patch.dict(
PROJECT_METADATA_DEFAULT_LABELS,
None
)
{'sector': mock_t('My Sector')},
) as mock_dict:
assert (
I18nUtils.get_metadata_field_label('sector', 'project', 'fr')
== MOCK_TRANSLATION_STRING
)
assert (
I18nUtils.get_metadata_field_label('sector', 'project')
== MOCK_TRANSLATION_STRING
)
assert (
PROJECT_METADATA_DEFAULT_LABELS['sector']
== MOCK_TRANSLATION_STRING
)
assert mock_t.call_args.args[0] == 'My Sector'
91 changes: 73 additions & 18 deletions hub/utils/i18n.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
# coding: utf-8
from __future__ import annotations

import copy
import json
import logging

import constance
from constance import config
from django.db.models import Q
from django.db.models.functions import Length
from django.utils.translation import get_language, gettext as t
from django_request_cache import cache_for_request

from kobo.apps.constance_backends.utils import to_python_object
from kobo.static_lists import (
PROJECT_METADATA_DEFAULT_LABELS,
USER_METADATA_DEFAULT_LABELS
)
from kpi.utils.log import logging
from ..models import SitewideMessage

Expand Down Expand Up @@ -57,9 +64,7 @@ def get_mfa_help_text(lang=None):
language = lang if lang else get_language()

try:
messages_dict = to_python_object(
constance.config.MFA_LOCALIZED_HELP_TEXT
)
messages_dict = to_python_object(config.MFA_LOCALIZED_HELP_TEXT)
except json.JSONDecodeError:
logging.error(
'Configuration value for MFA_LOCALIZED_HELP_TEXT has invalid '
Expand All @@ -81,25 +86,76 @@ def get_mfa_help_text(lang=None):
# either
message = t(messages_dict['default'])

message = message.replace(
'##support email##',
constance.config.SUPPORT_EMAIL,
)
message = message.replace('##support email##', config.SUPPORT_EMAIL)
return message

@staticmethod
def set_custom_label(
field: dict, default_label_dict: dict, lang: str = None,
) -> dict:
@classmethod
def get_metadata_field_label(
cls, field_name: str, field_type: str, lang: str = None
):
metadata_fields = {
field['name']: field
for field in cls.get_metadata_fields(
fields_type=field_type, lang=lang
)
}
return metadata_fields[field_name]['label']

@classmethod
@cache_for_request
def get_metadata_fields(
cls, fields_type: str, lang: str = None
) -> list[dict]:
"""
Return the translated label of the user metadata fields
Returns custom labels and translations for the metadata fields depending
on `metadata_fields_type` value (i.e. 'user' or 'project')
"""
# Get default value if lang is not specified
(
metadata_fields,
default_labels,
) = cls._get_metadata_fields_and_default_labels(fields_type)

language = lang if lang else get_language()

# This copy is to make unit tests work properly
field = copy.deepcopy(field)
# Check if each user metadata has a label
for metadata_field in metadata_fields:
if 'label' in metadata_field.keys():
cls._set_metadata_field_custom_label(
metadata_field, default_labels, language
)
else:
# If label is not available, use the default from static_list.py
# in `USER_METADATA_DEFAULT_LABELS` or `PROJECT_METADATA_DEFAULT_LABELS
try:
metadata_field['label'] = default_labels[metadata_field['name']]
except KeyError:
continue

return metadata_fields

@classmethod
def _get_metadata_fields_and_default_labels(
cls, fields_type: str
) -> tuple:

if fields_type == 'user':
return (
copy.deepcopy(to_python_object(config.USER_METADATA_FIELDS)),
USER_METADATA_DEFAULT_LABELS,
)
else:
return (
copy.deepcopy(to_python_object(config.PROJECT_METADATA_FIELDS)),
PROJECT_METADATA_DEFAULT_LABELS,
)

@staticmethod
def _set_metadata_field_custom_label(
field: dict, default_label_dict: dict, language: str,
):
"""
Returns the translated label of the metadata fields
"""
# Check to see if label exists
try:
label = field['label']
Expand All @@ -109,8 +165,7 @@ def set_custom_label(
# Use the default value if language is not available
translation = label['default']
except KeyError:
# Use kobo translated version
translation = default_label_dict[field['name']]

field['label'] = translation

return field