diff --git a/src/api/serializers.py b/src/api/serializers.py index f9caf721ff..a5fb400585 100755 --- a/src/api/serializers.py +++ b/src/api/serializers.py @@ -2,8 +2,10 @@ from rest_framework import serializers, validators +from django.conf import settings from django.db import transaction from django.shortcuts import reverse +from modeltranslation.utils import build_localized_fieldname from core import models as core_models, logic as core_logic from journal import models as journal_models @@ -13,6 +15,55 @@ from events import logic as event_logic +def translatable_repository(instance): + """Return the repository governing the active languages for a translatable + preprint object (Preprint, PreprintVersion or VersionQueue).""" + if hasattr(instance, "repository"): + return instance.repository + return instance.preprint.repository + + +def apply_translations(instance, field_name, translations): + """Set per-language values for a modeltranslation-managed field from a + ``{language_code: value}`` mapping. Does not save the instance.""" + if not translations: + return + for code, value in translations.items(): + setattr(instance, build_localized_fieldname(field_name, code), value) + + +class TranslationsField(serializers.DictField): + """A readable and writable ``{language_code: value}`` map for a + modeltranslation-managed field. On read it exposes one entry per language + the preprint's repository accepts; on write it accepts a partial mapping.""" + + def __init__(self, translated_field, **kwargs): + self.translated_field = translated_field + kwargs.setdefault("required", False) + kwargs.setdefault( + "child", + serializers.CharField(allow_blank=True, trim_whitespace=False), + ) + super().__init__(**kwargs) + + def get_attribute(self, instance): + # to_representation builds the map from the instance itself. + return instance + + def to_representation(self, instance): + repository = translatable_repository(instance) + languages = repository.languages or [settings.LANGUAGE_CODE] + return { + code: getattr( + instance, + build_localized_fieldname(self.translated_field, code), + "", + ) + or "" + for code in languages + } + + class LicenceSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = submission_models.Licence @@ -202,9 +253,14 @@ class Meta: "date_time", "title", "abstract", + "title_translations", + "abstract_translations", "public_download_url", ) + title_translations = TranslationsField("title", read_only=True) + abstract_translations = TranslationsField("abstract", read_only=True) + class PreprintSupplementaryFileSerializer(serializers.ModelSerializer): class Meta: @@ -372,6 +428,8 @@ class Meta: "pk", "title", "abstract", + "title_translations", + "abstract_translations", "stage", "license", "keywords", @@ -389,6 +447,8 @@ class Meta: ) depth = 2 + title_translations = TranslationsField("title", read_only=True) + abstract_translations = TranslationsField("abstract", read_only=True) authors = PreprintAccountSerializer( many=True, ) @@ -436,6 +496,15 @@ def create(self, validated_data): comments_editor=validated_data.get("comments_editor"), ) + apply_translations(preprint, "title", validated_data.get("title_translations")) + apply_translations( + preprint, "abstract", validated_data.get("abstract_translations") + ) + if validated_data.get("title_translations") or validated_data.get( + "abstract_translations" + ): + preprint.save() + for i, author_data in enumerate(validated_data.get("authors", [])): author_email = author_data.pop("email").lower() author, created = core_models.Account.objects.get_or_create( @@ -530,6 +599,10 @@ def update(self, instance, validated_data): instance.doi = validated_data.get("doi") instance.preprint_doi = validated_data.get("preprint_doi") instance.comments_editor = validated_data.get("comments_editor") + apply_translations(instance, "title", validated_data.get("title_translations")) + apply_translations( + instance, "abstract", validated_data.get("abstract_translations") + ) instance.save() authors = [] @@ -633,6 +706,8 @@ class Meta: "authors", "title", "abstract", + "title_translations", + "abstract_translations", "stage", "license", "keywords", @@ -649,6 +724,8 @@ class Meta: "comments_editor", ) + title_translations = TranslationsField("title") + abstract_translations = TranslationsField("abstract") authors = PreprintAccountSerializer( many=True, ) @@ -689,10 +766,15 @@ class Meta: "update_type", "title", "abstract", + "title_translations", + "abstract_translations", "published_doi", "file", ) + title_translations = TranslationsField("title") + abstract_translations = TranslationsField("abstract") + def validate(self, data): request = self.context.get("request", None) preprint = data.get("preprint") @@ -707,6 +789,16 @@ def validate(self, data): return data + def create(self, validated_data): + title_translations = validated_data.pop("title_translations", None) + abstract_translations = validated_data.pop("abstract_translations", None) + version_queue = super().create(validated_data) + apply_translations(version_queue, "title", title_translations) + apply_translations(version_queue, "abstract", abstract_translations) + if title_translations or abstract_translations: + version_queue.save() + return version_queue + class VersionQueueSerializer(serializers.ModelSerializer): class Meta: @@ -720,9 +812,14 @@ class Meta: "published_doi", "title", "abstract", + "title_translations", + "abstract_translations", "file", ) + title_translations = TranslationsField("title", read_only=True) + abstract_translations = TranslationsField("abstract", read_only=True) + class RegisterAccountSerializer(serializers.ModelSerializer): class Meta: diff --git a/src/api/tests/test_preprints.py b/src/api/tests/test_preprints.py index a9771f2547..b4db8a751d 100644 --- a/src/api/tests/test_preprints.py +++ b/src/api/tests/test_preprints.py @@ -16,6 +16,7 @@ from rest_framework.test import APIClient +from api import serializers as api_serializers from identifiers import forms as identifier_forms from identifiers import models as identifier_models from repository import models as repository_models @@ -522,3 +523,122 @@ def test_owner_sees_only_own_files(self): } self.assertNotIn(self.preprint_other.pk, preprint_ids) self.api_client.force_authenticate(user=None) + + +MULTILINGUAL_API_DOMAIN = "preprint-api-multilingual.domain.com" +MONOLINGUAL_API_DOMAIN = "preprint-api-monolingual.domain.com" + + +class TestPreprintMultilingualAPI(TestCase): + """ + Regression tests for multilingual title/abstract support in the preprint + API serializers (#3375). The repository uses django-modeltranslation, so + the serializers expose and accept a {language_code: value} map alongside + the primary title/abstract. + """ + + @classmethod + def setUpTestData(cls): + cls.press = helpers.create_press() + cls.author = helpers.create_user("preprint.api.multilingual@test.com") + + cls.repo, cls.subject = helpers.create_repository( + cls.press, [], [], domain=MULTILINGUAL_API_DOMAIN + ) + cls.repo.languages = ["en", "es"] + cls.repo.default_language = "en" + cls.repo.save() + + cls.mono_repo, cls.mono_subject = helpers.create_repository( + cls.press, [], [], domain=MONOLINGUAL_API_DOMAIN + ) + cls.mono_repo.languages = ["en"] + cls.mono_repo.save() + + cls.preprint = helpers.create_preprint( + cls.repo, cls.author, cls.subject, title="English Title" + ) + cls.preprint.title_en = "English Title" + cls.preprint.title_es = "Título en español" + cls.preprint.abstract_en = "English abstract" + cls.preprint.abstract_es = "Resumen en español" + cls.preprint.save() + + cls.mono_preprint = helpers.create_preprint( + cls.mono_repo, cls.author, cls.mono_subject, title="Mono Title" + ) + + def test_read_serializer_exposes_translations_for_active_languages(self): + """The read serializer returns one entry per repository language.""" + data = api_serializers.PreprintSerializer(self.preprint).data + self.assertEqual( + data["title_translations"], + {"en": "English Title", "es": "Título en español"}, + ) + self.assertEqual( + data["abstract_translations"], + {"en": "English abstract", "es": "Resumen en español"}, + ) + + def test_read_serializer_single_language_repository(self): + """A single-language repository exposes a single translation entry.""" + data = api_serializers.PreprintSerializer(self.mono_preprint).data + self.assertEqual( + data["title_translations"], + {"en": "Mono Title"}, + ) + + def test_update_serializer_applies_translations(self): + """Updating via the create/update serializer writes per-language fields.""" + validated_data = { + "title": "Updated English", + "abstract": "Updated English abstract", + "title_translations": { + "en": "Updated English", + "es": "Actualizado en español", + }, + "abstract_translations": {"es": "Resumen actualizado"}, + "owner": self.author, + "repository": self.repo, + "stage": repository_models.STAGE_PREPRINT_REVIEW, + "license": None, + "date_submitted": self.preprint.date_submitted, + "date_accepted": None, + "date_published": None, + "doi": None, + "preprint_doi": None, + "comments_editor": "", + "authors": [], + "keywords": [], + "subject": [], + "repositoryfieldanswer_set": [], + "preprintsupplementaryfile_set": [], + } + serializer = api_serializers.PreprintCreateSerializer() + result = serializer.update(self.preprint, validated_data) + result.refresh_from_db() + + self.assertEqual(result.title_en, "Updated English") + self.assertEqual(result.title_es, "Actualizado en español") + self.assertEqual(result.abstract_es, "Resumen actualizado") + + def test_version_queue_create_applies_translations(self): + """Creating a version queue entry writes per-language fields.""" + serializer = api_serializers.VersionQueueCreateSerializer() + version_queue = serializer.create( + { + "preprint": self.preprint, + "update_type": "correction", + "title": "Version English", + "abstract": "Version English abstract", + "title_translations": { + "en": "Version English", + "es": "Versión en español", + }, + "abstract_translations": {"es": "Resumen de la versión"}, + } + ) + version_queue.refresh_from_db() + + self.assertEqual(version_queue.title_es, "Versión en español") + self.assertEqual(version_queue.abstract_es, "Resumen de la versión") diff --git a/src/journal/middleware.py b/src/journal/middleware.py index 2b284ffd17..b0b5c0ebf4 100644 --- a/src/journal/middleware.py +++ b/src/journal/middleware.py @@ -47,3 +47,20 @@ def process_request(request): request.available_languages = set(available_languages) request.default_language = default_language request.current_language = translation.get_language() + + elif getattr(request, "repository", None) and settings.USE_I18N: + current_language = translation.get_language() + available_languages = list(request.repository.languages or []) + default_language = ( + request.repository.default_language or settings.LANGUAGE_CODE + ) + + if current_language not in available_languages: + translation.activate(default_language) + + if not available_languages: + available_languages = [lang[0] for lang in settings.LANGUAGES] + + request.available_languages = set(available_languages) + request.default_language = default_language + request.current_language = translation.get_language() diff --git a/src/repository/forms.py b/src/repository/forms.py index b22cb849e9..d712bea193 100755 --- a/src/repository/forms.py +++ b/src/repository/forms.py @@ -4,6 +4,7 @@ from django.contrib.admin.widgets import FilteredSelectMultiple from django.utils.text import slugify from django.contrib import messages +from modeltranslation.utils import build_localized_fieldname from tinymce.widgets import TinyMCE @@ -90,6 +91,7 @@ class Meta: fields = ( "title", "abstract", + "language", "license", "comments_editor", "subject", @@ -108,6 +110,53 @@ def __init__(self, *args, **kwargs): self.submission_type_slug = kwargs.pop("submission_type_slug", None) super(PreprintInfo, self).__init__(*args, **kwargs) + repository = self.request.repository + active_languages = repository.languages or [settings.LANGUAGE_CODE] + lang_dict = dict(settings.LANGUAGES) + primary_language = repository.default_language or settings.LANGUAGE_CODE + is_multilingual = len(active_languages) > 1 + + self.language_field_names = [] + if is_multilingual: + self.fields.pop("title", None) + self.fields.pop("abstract", None) + for code in active_languages: + lang_name = lang_dict.get(code, code) + title_field = build_localized_fieldname("title", code) + abstract_field = build_localized_fieldname("abstract", code) + self.fields[title_field] = forms.CharField( + max_length=300, + required=(code == primary_language), + label=_("Title ({})").format(lang_name), + widget=forms.TextInput(attrs={"placeholder": _("Title")}), + ) + self.fields[abstract_field] = forms.CharField( + required=False, + label=_("Abstract ({})").format(lang_name), + widget=forms.Textarea( + attrs={"placeholder": _("Enter your article's abstract here")} + ), + ) + if self.instance and self.instance.pk: + self.initial[title_field] = getattr(self.instance, title_field, "") + self.initial[abstract_field] = getattr( + self.instance, abstract_field, "" + ) + self.language_field_names.extend([title_field, abstract_field]) + + if is_multilingual: + self.fields["language"] = forms.ChoiceField( + choices=[ + (code, lang_dict.get(code, code)) for code in active_languages + ], + required=True, + label=_("Language"), + help_text=_("The primary language of this preprint."), + ) + else: + self.initial["language"] = active_languages[0] + self.fields["language"].widget = forms.HiddenInput() + if ( not self.submission_type_slug and self.instance @@ -145,7 +194,7 @@ def __init__(self, *args, **kwargs): ) elif element.input_type == "textarea": self.fields[element.name] = forms.CharField( - widget=forms.Textarea, + widget=forms.Textarea(), required=element.required, ) elif element.input_type == "date": @@ -209,6 +258,10 @@ def save(self, commit=True): preprint.repository = self.request.repository + for field_name in self.language_field_names: + if field_name in self.cleaned_data: + setattr(preprint, field_name, self.cleaned_data[field_name]) + if self.request: additional_fields = models.RepositoryField.objects.filter( repository=self.request.repository, @@ -485,14 +538,48 @@ class Meta: def __init__(self, *args, **kwargs): self.preprint = kwargs.pop("preprint") super(VersionForm, self).__init__(*args, **kwargs) - self.fields["title"].initial = self.preprint.title - self.fields["abstract"].initial = self.preprint.abstract + + repository = self.preprint.repository + active_languages = repository.languages or [settings.LANGUAGE_CODE] + lang_dict = dict(settings.LANGUAGES) + + self.language_field_names = [] + if len(active_languages) > 1: + self.fields.pop("title", None) + self.fields.pop("abstract", None) + for code in active_languages: + lang_name = lang_dict.get(code, code) + title_field = build_localized_fieldname("title", code) + abstract_field = build_localized_fieldname("abstract", code) + self.fields[title_field] = forms.CharField( + max_length=300, + required=False, + label=_("Title ({})").format(lang_name), + ) + self.fields[abstract_field] = forms.CharField( + required=False, + label=_("Abstract ({})").format(lang_name), + widget=forms.Textarea(), + ) + self.initial[title_field] = getattr(self.preprint, title_field, "") + self.initial[abstract_field] = getattr( + self.preprint, abstract_field, "" + ) + self.language_field_names.extend([title_field, abstract_field]) + else: + self.fields["title"].initial = self.preprint.title + self.fields["abstract"].initial = self.preprint.abstract + self.fields["published_doi"].initial = self.preprint.doi def save(self, commit=True): version = super(VersionForm, self).save(commit=False) version.preprint = self.preprint + for field_name in self.language_field_names: + if field_name in self.cleaned_data: + setattr(version, field_name, self.cleaned_data[field_name]) + if commit: version.save() diff --git a/src/repository/migrations/0057_add_multilingual_support.py b/src/repository/migrations/0057_add_multilingual_support.py new file mode 100644 index 0000000000..e3882063e1 --- /dev/null +++ b/src/repository/migrations/0057_add_multilingual_support.py @@ -0,0 +1,303 @@ +# Generated by Django 4.2.29 on 2026-05-27 12:25 + +import core.model_utils +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("repository", "0056_preprintfile_text"), + ] + + operations = [ + migrations.AddField( + model_name="historicalrepository", + name="default_language", + field=models.CharField( + blank=True, + default="en", + help_text="The primary language of this repository.", + max_length=20, + ), + ), + migrations.AddField( + model_name="historicalrepository", + name="languages", + field=models.JSONField( + blank=True, + default=list, + help_text="List of language codes accepted by this repository, sourced from settings.LANGUAGES.", + ), + ), + migrations.AddField( + model_name="preprint", + name="abstract_cy", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="preprint", + name="abstract_de", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="preprint", + name="abstract_en", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="preprint", + name="abstract_en_us", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="preprint", + name="abstract_es", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="preprint", + name="abstract_fr", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="preprint", + name="abstract_nl", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="preprint", + name="title_cy", + field=models.CharField( + help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="preprint", + name="title_de", + field=models.CharField( + help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="preprint", + name="title_en", + field=models.CharField( + help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="preprint", + name="title_en_us", + field=models.CharField( + help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="preprint", + name="title_es", + field=models.CharField( + help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="preprint", + name="title_fr", + field=models.CharField( + help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="preprint", + name="title_nl", + field=models.CharField( + help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="preprintversion", + name="abstract_cy", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="preprintversion", + name="abstract_de", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="preprintversion", + name="abstract_en", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="preprintversion", + name="abstract_en_us", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="preprintversion", + name="abstract_es", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="preprintversion", + name="abstract_fr", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="preprintversion", + name="abstract_nl", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="preprintversion", + name="title_cy", + field=models.CharField( + blank=True, help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="preprintversion", + name="title_de", + field=models.CharField( + blank=True, help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="preprintversion", + name="title_en", + field=models.CharField( + blank=True, help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="preprintversion", + name="title_en_us", + field=models.CharField( + blank=True, help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="preprintversion", + name="title_es", + field=models.CharField( + blank=True, help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="preprintversion", + name="title_fr", + field=models.CharField( + blank=True, help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="preprintversion", + name="title_nl", + field=models.CharField( + blank=True, help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="repository", + name="default_language", + field=models.CharField( + blank=True, + default="en", + help_text="The primary language of this repository.", + max_length=20, + ), + ), + migrations.AddField( + model_name="repository", + name="languages", + field=models.JSONField( + blank=True, + default=list, + help_text="List of language codes accepted by this repository, sourced from settings.LANGUAGES.", + ), + ), + migrations.AddField( + model_name="versionqueue", + name="abstract_cy", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="versionqueue", + name="abstract_de", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="versionqueue", + name="abstract_en", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="versionqueue", + name="abstract_en_us", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="versionqueue", + name="abstract_es", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="versionqueue", + name="abstract_fr", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="versionqueue", + name="abstract_nl", + field=core.model_utils.JanewayBleachField(blank=True, null=True), + ), + migrations.AddField( + model_name="versionqueue", + name="title_cy", + field=models.CharField( + help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="versionqueue", + name="title_de", + field=models.CharField( + help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="versionqueue", + name="title_en", + field=models.CharField( + help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="versionqueue", + name="title_en_us", + field=models.CharField( + help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="versionqueue", + name="title_es", + field=models.CharField( + help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="versionqueue", + name="title_fr", + field=models.CharField( + help_text="Your article title", max_length=300, null=True + ), + ), + migrations.AddField( + model_name="versionqueue", + name="title_nl", + field=models.CharField( + help_text="Your article title", max_length=300, null=True + ), + ), + ] diff --git a/src/repository/migrations/0058_add_preprint_language_field.py b/src/repository/migrations/0058_add_preprint_language_field.py new file mode 100644 index 0000000000..03938c76b5 --- /dev/null +++ b/src/repository/migrations/0058_add_preprint_language_field.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.29 on 2026-05-27 13:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("repository", "0057_add_multilingual_support"), + ] + + operations = [ + migrations.AddField( + model_name="preprint", + name="language", + field=models.CharField( + blank=True, + default="en", + help_text="The primary language of this preprint.", + max_length=20, + ), + ), + ] diff --git a/src/repository/models.py b/src/repository/models.py index c10859e3da..1b297f3344 100755 --- a/src/repository/models.py +++ b/src/repository/models.py @@ -33,6 +33,7 @@ from django.utils.html import format_html from django.core.validators import RegexValidator +from modeltranslation.utils import build_localized_fieldname from openpyxl import load_workbook from simple_history.models import HistoricalRecords import swapper @@ -363,6 +364,20 @@ class Repository(model_utils.AbstractSiteModel): default=False, help_text="Enable to use Crossref test.", ) + languages = models.JSONField( + default=list, + blank=True, + help_text=_( + "List of language codes accepted by this repository, " + "sourced from settings.LANGUAGES." + ), + ) + default_language = models.CharField( + max_length=20, + blank=True, + default="en", + help_text=_("The primary language of this repository."), + ) class Meta: verbose_name_plural = "repositories" @@ -846,6 +861,12 @@ class Preprint(models.Model): null=True, on_delete=models.SET_NULL, ) + language = models.CharField( + max_length=20, + blank=True, + default="en", + help_text=_("The primary language of this preprint."), + ) comments_editor = models.TextField( blank=True, null=True, @@ -901,6 +922,29 @@ def __str__(self): self.title, ) + @property + def language_name(self): + lang_dict = dict(settings.LANGUAGES) + return lang_dict.get(self.language, self.language or "") + + def translated_metadata(self): + active_languages = self.repository.languages if self.repository else [] + if len(active_languages) <= 1: + return [] + lang_dict = dict(settings.LANGUAGES) + result = [] + for code in active_languages: + title_field = build_localized_fieldname("title", code) + abstract_field = build_localized_fieldname("abstract", code) + result.append( + { + "language": lang_dict.get(code, code), + "title": getattr(self, title_field, "") or "", + "abstract": getattr(self, abstract_field, "") or "", + } + ) + return result + def old_versions(self): return PreprintVersion.objects.filter( preprint=self, @@ -1881,12 +1925,32 @@ def approve(self): self.approved = True current_version = None + active_languages = self.preprint.repository.languages or list( + dict(settings.LANGUAGES).keys() + ) + translated_fields = [ + ( + build_localized_fieldname("title", code), + build_localized_fieldname("abstract", code), + ) + for code in active_languages + ] + # Update the current version to have the Preprint's current title # and abstract. if self.preprint.current_version is not None: current_version = self.preprint.current_version - current_version.title = self.preprint.title - current_version.abstract = self.preprint.abstract + for title_field, abstract_field in translated_fields: + setattr( + current_version, + title_field, + getattr(self.preprint, title_field, ""), + ) + setattr( + current_version, + abstract_field, + getattr(self.preprint, abstract_field, ""), + ) current_version.published_doi = self.preprint.doi this_file = self.preprint.current_version.file # no version yet @@ -1905,11 +1969,13 @@ def approve(self): ) # Overwrite the preprint's metadata now we have a historical record. - # Check that title and abstract have value, if not there is no change. - if self.title: - self.preprint.title = self.title - if self.abstract: - self.preprint.abstract = self.abstract + for title_field, abstract_field in translated_fields: + title_val = getattr(self, title_field, "") + abstract_val = getattr(self, abstract_field, "") + if title_val: + setattr(self.preprint, title_field, title_val) + if abstract_val: + setattr(self.preprint, abstract_field, abstract_val) if self.published_doi: self.preprint.doi = self.published_doi @@ -1937,6 +2003,28 @@ def status(self): else: return _("Declined") + def translated_metadata(self): + active_languages = ( + self.preprint.repository.languages + if self.preprint and self.preprint.repository + else [] + ) + if len(active_languages) <= 1: + return [] + lang_dict = dict(settings.LANGUAGES) + result = [] + for code in active_languages: + title_field = build_localized_fieldname("title", code) + abstract_field = build_localized_fieldname("abstract", code) + result.append( + { + "language": lang_dict.get(code, code), + "title": getattr(self, title_field, "") or "", + "abstract": getattr(self, abstract_field, "") or "", + } + ) + return result + @property def safe_title(self): if self.title: diff --git a/src/repository/tests/test_languages.py b/src/repository/tests/test_languages.py new file mode 100644 index 0000000000..42381b146d --- /dev/null +++ b/src/repository/tests/test_languages.py @@ -0,0 +1,347 @@ +from django.conf import settings +from django.test import TestCase, override_settings +from django.shortcuts import reverse +from django.urls.base import clear_script_prefix + +from utils.testing import helpers +from repository import models as rm + + +class TestRepositoryLanguages(TestCase): + @classmethod + def setUpTestData(cls): + cls.press = helpers.create_press() + cls.press.save() + cls.repo_manager = helpers.create_user("lang_manager@janeway.systems") + cls.repo_manager.is_active = True + cls.repo_manager.save() + cls.server_name = "lang-repo.test.com" + cls.repository, cls.subject = helpers.create_repository( + cls.press, + [cls.repo_manager], + [], + domain=cls.server_name, + ) + + def setUp(self): + clear_script_prefix() + self.repository.languages = ["en"] + self.repository.default_language = "en" + self.repository.save() + + @override_settings(URL_CONFIG="domain") + def test_default_language_is_english(self): + self.assertEqual(self.repository.default_language, "en") + self.assertEqual(self.repository.languages, ["en"]) + + @override_settings(URL_CONFIG="domain") + def test_enable_language(self): + self.client.force_login(self.repo_manager) + self.client.post( + reverse("repository_languages"), + {"enable": "es"}, + SERVER_NAME=self.server_name, + ) + self.repository.refresh_from_db() + self.assertIn("es", self.repository.languages) + self.assertIn("en", self.repository.languages) + + @override_settings(URL_CONFIG="domain") + def test_disable_language(self): + self.repository.languages = ["en", "es"] + self.repository.save() + self.client.force_login(self.repo_manager) + self.client.post( + reverse("repository_languages"), + {"disable": "es"}, + SERVER_NAME=self.server_name, + ) + self.repository.refresh_from_db() + self.assertNotIn("es", self.repository.languages) + self.assertIn("en", self.repository.languages) + + @override_settings(URL_CONFIG="domain") + def test_set_default_language(self): + self.repository.languages = ["en", "es"] + self.repository.save() + self.client.force_login(self.repo_manager) + self.client.post( + reverse("repository_languages"), + {"default": "es"}, + SERVER_NAME=self.server_name, + ) + self.repository.refresh_from_db() + self.assertEqual(self.repository.default_language, "es") + + @override_settings(URL_CONFIG="domain") + def test_set_default_to_inactive_language_rejected(self): + self.client.force_login(self.repo_manager) + self.client.post( + reverse("repository_languages"), + {"default": "de"}, + SERVER_NAME=self.server_name, + ) + self.repository.refresh_from_db() + self.assertEqual(self.repository.default_language, "en") + + @override_settings(URL_CONFIG="domain") + def test_disable_all_languages_falls_back_to_default(self): + self.client.force_login(self.repo_manager) + self.client.post( + reverse("repository_languages"), + {"disable": "en"}, + SERVER_NAME=self.server_name, + ) + self.repository.refresh_from_db() + self.assertIn(settings.LANGUAGE_CODE, self.repository.languages) + + @override_settings(URL_CONFIG="domain") + def test_default_language_reset_when_disabled(self): + self.repository.languages = ["en", "es"] + self.repository.default_language = "es" + self.repository.save() + self.client.force_login(self.repo_manager) + self.client.post( + reverse("repository_languages"), + {"disable": "es"}, + SERVER_NAME=self.server_name, + ) + self.repository.refresh_from_db() + self.assertEqual(self.repository.default_language, "en") + + +class TestPreprintTranslation(TestCase): + @classmethod + def setUpTestData(cls): + cls.press = helpers.create_press() + cls.press.save() + cls.repo_manager = helpers.create_user("lang_trans_mgr@janeway.systems") + cls.repo_manager.is_active = True + cls.repo_manager.save() + cls.author = helpers.create_user("lang_author@janeway.systems") + cls.author.is_active = True + cls.author.save() + cls.server_name = "lang-trans.test.com" + cls.repository, cls.subject = helpers.create_repository( + cls.press, + [cls.repo_manager], + [], + domain=cls.server_name, + ) + cls.preprint = helpers.create_preprint( + cls.repository, + cls.author, + cls.subject, + ) + + def setUp(self): + clear_script_prefix() + + def test_preprint_translation_saves(self): + self.preprint.title_en = "English Title" + self.preprint.title_es = "Título en Español" + self.preprint.abstract_en = "English abstract" + self.preprint.abstract_es = "Resumen en español" + self.preprint.save() + self.preprint.refresh_from_db() + self.assertEqual(self.preprint.title_en, "English Title") + self.assertEqual(self.preprint.title_es, "Título en Español") + self.assertEqual(self.preprint.abstract_en, "English abstract") + self.assertEqual(self.preprint.abstract_es, "Resumen en español") + + def test_translated_metadata_empty_for_single_language(self): + self.repository.languages = ["en"] + self.repository.save() + self.preprint.repository = self.repository + self.assertEqual(self.preprint.translated_metadata(), []) + + def test_translated_metadata_returns_entry_per_active_language(self): + self.repository.languages = ["en", "es"] + self.repository.save() + self.preprint.repository = self.repository + self.preprint.title_en = "English Title" + self.preprint.title_es = "Título en Español" + self.preprint.abstract_en = "English abstract" + self.preprint.abstract_es = "Resumen en español" + metadata = self.preprint.translated_metadata() + self.assertEqual(len(metadata), 2) + by_title = {entry["title"]: entry for entry in metadata} + self.assertIn("English Title", by_title) + self.assertIn("Título en Español", by_title) + self.assertEqual( + by_title["Título en Español"]["abstract"], + "Resumen en español", + ) + + def test_form_shows_per_language_fields_when_multiple(self): + from repository.forms import PreprintInfo + + self.repository.languages = ["en", "es"] + self.repository.save() + request = helpers.Request() + request.press = self.press + request.repository = self.repository + form = PreprintInfo( + instance=self.preprint, + request=request, + submission_type_slug=None, + ) + self.assertIn("title_en", form.fields) + self.assertIn("title_es", form.fields) + self.assertIn("abstract_en", form.fields) + self.assertIn("abstract_es", form.fields) + self.assertNotIn("title", form.fields) + self.assertNotIn("abstract", form.fields) + + def test_form_shows_base_fields_when_single_language(self): + from repository.forms import PreprintInfo + + self.repository.languages = ["en"] + self.repository.save() + request = helpers.Request() + request.press = self.press + request.repository = self.repository + form = PreprintInfo( + instance=self.preprint, + request=request, + submission_type_slug=None, + ) + self.assertIn("title", form.fields) + self.assertIn("abstract", form.fields) + self.assertNotIn("title_en", form.fields) + self.assertNotIn("title_es", form.fields) + + def test_form_primary_language_title_required(self): + from repository.forms import PreprintInfo + + self.repository.languages = ["en", "es"] + self.repository.save() + request = helpers.Request() + request.press = self.press + request.repository = self.repository + form = PreprintInfo( + instance=self.preprint, + request=request, + submission_type_slug=None, + ) + self.assertTrue(form.fields["title_en"].required) + self.assertFalse(form.fields["title_es"].required) + + def test_form_required_title_follows_repository_default_language(self): + from repository.forms import PreprintInfo + + self.repository.languages = ["en", "es"] + self.repository.default_language = "es" + self.repository.save() + request = helpers.Request() + request.press = self.press + request.repository = self.repository + form = PreprintInfo( + instance=self.preprint, + request=request, + submission_type_slug=None, + ) + self.assertTrue(form.fields["title_es"].required) + self.assertFalse(form.fields["title_en"].required) + + def test_form_language_dropdown_with_multiple_languages(self): + from repository.forms import PreprintInfo + + self.repository.languages = ["en", "es"] + self.repository.save() + request = helpers.Request() + request.press = self.press + request.repository = self.repository + form = PreprintInfo( + instance=self.preprint, + request=request, + submission_type_slug=None, + ) + self.assertIn("language", form.fields) + choice_values = [c[0] for c in form.fields["language"].choices] + self.assertEqual(choice_values, ["en", "es"]) + + def test_form_language_hidden_with_single_language(self): + from repository.forms import PreprintInfo + from django.forms import HiddenInput + + self.repository.languages = ["en"] + self.repository.save() + request = helpers.Request() + request.press = self.press + request.repository = self.repository + form = PreprintInfo( + instance=self.preprint, + request=request, + submission_type_slug=None, + ) + self.assertIsInstance(form.fields["language"].widget, HiddenInput) + self.assertEqual(form.initial["language"], "en") + + def test_version_form_shows_per_language_fields_when_multiple(self): + from repository.forms import VersionForm + + self.repository.languages = ["en", "es"] + self.repository.save() + self.preprint.repository = self.repository + form = VersionForm(preprint=self.preprint) + self.assertIn("title_en", form.fields) + self.assertIn("title_es", form.fields) + self.assertIn("abstract_en", form.fields) + self.assertIn("abstract_es", form.fields) + self.assertNotIn("title", form.fields) + self.assertNotIn("abstract", form.fields) + + def test_version_form_shows_base_fields_when_single_language(self): + from repository.forms import VersionForm + + self.repository.languages = ["en"] + self.repository.save() + self.preprint.repository = self.repository + form = VersionForm(preprint=self.preprint) + self.assertIn("title", form.fields) + self.assertIn("abstract", form.fields) + self.assertNotIn("title_en", form.fields) + self.assertNotIn("title_es", form.fields) + + +class TestRepositoryLanguageMiddleware(TestCase): + @classmethod + def setUpTestData(cls): + cls.press = helpers.create_press() + cls.press.save() + cls.repo_manager = helpers.create_user("lang_mw_mgr@janeway.systems") + cls.repo_manager.is_active = True + cls.repo_manager.save() + cls.server_name = "lang-mw.test.com" + cls.repository, cls.subject = helpers.create_repository( + cls.press, + [cls.repo_manager], + [], + domain=cls.server_name, + ) + + def setUp(self): + clear_script_prefix() + self.repository.languages = ["en", "es"] + self.repository.default_language = "en" + self.repository.save() + + @override_settings(URL_CONFIG="domain") + def test_middleware_sets_available_languages(self): + self.client.force_login(self.repo_manager) + response = self.client.get( + reverse("repository_languages"), + SERVER_NAME=self.server_name, + ) + self.assertIn("en", response.wsgi_request.available_languages) + self.assertIn("es", response.wsgi_request.available_languages) + + @override_settings(URL_CONFIG="domain") + def test_middleware_sets_default_language(self): + self.client.force_login(self.repo_manager) + response = self.client.get( + reverse("repository_languages"), + SERVER_NAME=self.server_name, + ) + self.assertEqual(response.wsgi_request.default_language, "en") diff --git a/src/repository/translation.py b/src/repository/translation.py new file mode 100644 index 0000000000..f77f12b0f5 --- /dev/null +++ b/src/repository/translation.py @@ -0,0 +1,18 @@ +from modeltranslation.translator import register, TranslationOptions + +from repository import models + + +@register(models.Preprint) +class PreprintTranslationOptions(TranslationOptions): + fields = ("title", "abstract") + + +@register(models.PreprintVersion) +class PreprintVersionTranslationOptions(TranslationOptions): + fields = ("title", "abstract") + + +@register(models.VersionQueue) +class VersionQueueTranslationOptions(TranslationOptions): + fields = ("title", "abstract") diff --git a/src/repository/urls.py b/src/repository/urls.py index 40b1f1ae1b..a98084d641 100755 --- a/src/repository/urls.py +++ b/src/repository/urls.py @@ -236,6 +236,11 @@ re_path( r"^manager/licenses/$", views.repository_licenses, name="repository_licenses" ), + re_path( + r"^manager/languages/$", + views.repository_languages, + name="repository_languages", + ), re_path( r"^manager/subjects/$", views.repository_subjects, name="repository_subjects" ), diff --git a/src/repository/views.py b/src/repository/views.py index 8edd100b1c..dee0fc24cd 100644 --- a/src/repository/views.py +++ b/src/repository/views.py @@ -6,8 +6,9 @@ from datetime import datetime from dateutil import tz +from django.conf import settings from django.shortcuts import render, redirect, get_object_or_404 -from django.utils import timezone +from django.utils import timezone, translation from django.db.models import Q, Count from django.db.models.query import RawQuerySet from django.urls import reverse @@ -1707,6 +1708,12 @@ def repository_wizard(request, short_name=None, step="1"): ) ) + active_language_names = [] + if repository and step == "3": + lang_dict = dict(settings.LANGUAGES) + active_codes = repository.languages or [settings.LANGUAGE_CODE] + active_language_names = [lang_dict.get(code, code) for code in active_codes] + template = "admin/repository/wizard.html" context = { "repository": repository, @@ -1715,6 +1722,7 @@ def repository_wizard(request, short_name=None, step="1"): "help_template": "admin/elements/repository/{step}_help.html".format( step=step, ), + "repository_active_language_names": active_language_names, } return render(request, template, context) @@ -2529,6 +2537,87 @@ def repository_licenses(request): ) +@is_repository_manager +def repository_languages(request): + repository = request.repository + active_languages = repository.languages or [settings.LANGUAGE_CODE] + + if request.POST: + if "default" in request.POST: + new_default = request.POST.get("default") + if new_default in active_languages: + repository.default_language = new_default + messages.add_message( + request, + messages.SUCCESS, + "Default language now set to {}.".format(new_default), + ) + else: + messages.add_message( + request, + messages.WARNING, + "{} is not an active language for this repository.".format( + new_default + ), + ) + if "enable" in request.POST: + lang_to_enable = request.POST.get("enable") + if lang_to_enable not in dict(settings.LANGUAGES): + messages.add_message( + request, + messages.WARNING, + "{} is not a valid language.".format(lang_to_enable), + ) + else: + if lang_to_enable not in active_languages: + active_languages.append(lang_to_enable) + messages.add_message( + request, + messages.SUCCESS, + "{} enabled.".format(lang_to_enable), + ) + if "disable" in request.POST: + lang_to_disable = request.POST.get("disable") + if lang_to_disable in active_languages: + active_languages.remove(lang_to_disable) + messages.add_message( + request, + messages.SUCCESS, + "{} disabled.".format(lang_to_disable), + ) + else: + messages.add_message( + request, + messages.WARNING, + "{} is not an active language for this repository.".format( + lang_to_disable + ), + ) + + if not active_languages: + active_languages.append(settings.LANGUAGE_CODE) + + repository.languages = active_languages + if repository.default_language not in active_languages: + repository.default_language = active_languages[0] + repository.save() + return redirect(reverse("repository_languages")) + + all_languages = settings.LANGUAGES + lang_dict = dict(all_languages) + active_language_details = [ + (code, lang_dict.get(code, code)) for code in active_languages + ] + + template = "admin/repository/languages.html" + context = { + "active_languages": active_languages, + "active_language_details": active_language_details, + "language_choices": all_languages, + } + return render(request, template, context) + + @is_repository_manager def send_user_email(request, user_id, preprint_id): user = get_object_or_404(core_models.Account, pk=user_id) diff --git a/src/templates/admin/elements/repository/1_help.html b/src/templates/admin/elements/repository/1_help.html index a9ea5f8c7d..ae35a1d586 100644 --- a/src/templates/admin/elements/repository/1_help.html +++ b/src/templates/admin/elements/repository/1_help.html @@ -17,4 +17,4 @@ - \ No newline at end of file + diff --git a/src/templates/admin/elements/repository/metadata_form.html b/src/templates/admin/elements/repository/metadata_form.html index f92cd73deb..cd58618f65 100644 --- a/src/templates/admin/elements/repository/metadata_form.html +++ b/src/templates/admin/elements/repository/metadata_form.html @@ -3,14 +3,31 @@ {% load field %} {% include "elements/forms/errors.html" with form=form %} -
- {{ form.title|foundation }} -
+{% if form.language_field_names %} +
+
+

{% trans "This repository accepts submissions in multiple languages. Please provide a title and abstract in the primary language. Translations are optional." %}

+
+
+ {% for field_name in form.language_field_names %} + {% get_form_field form field_name as lang_field %} +
+ {{ lang_field|foundation }} +
+ {% endfor %} +{% else %} +
+ {{ form.title|foundation }} +
+
+ {{ form.abstract|foundation }} +
+{% endif %}
{{ form.submission_type|foundation }}
- {{ form.abstract|foundation }} + {{ form.language|foundation }}
{{ form.license|foundation }} diff --git a/src/templates/admin/elements/repository/version_detail.html b/src/templates/admin/elements/repository/version_detail.html index 6f2fdcd3ba..086d84eff6 100644 --- a/src/templates/admin/elements/repository/version_detail.html +++ b/src/templates/admin/elements/repository/version_detail.html @@ -16,10 +16,21 @@

{{ preprint.title|safe }} Version Detail

Current Metadata

-

Title

-

{{ version.preprint.title|safe }}

-

Abstract

-

{{ version.preprint.abstract|safe|linebreaksbr }}

+ {% with version.preprint.translated_metadata as current_meta %} + {% if current_meta %} + {% for meta in current_meta %} +

Title ({{ meta.language }})

+

{{ meta.title|default:"—" }}

+

Abstract ({{ meta.language }})

+

{{ meta.abstract|default:"—"|safe|linebreaksbr }}

+ {% endfor %} + {% else %} +

Title

+

{{ version.preprint.title|safe }}

+

Abstract

+

{{ version.preprint.abstract|safe|linebreaksbr }}

+ {% endif %} + {% endwith %}

Published DOI

{{ version.preprint.doi }}

File

@@ -33,10 +44,21 @@

Current Metadata

New Metadata

-

Title{% if version.title and version.title != version.preprint.title %} (changed){% endif %}

-

{% if version.title == version.preprint.title %}Unchanged{% else %} {{ version.title }}{% endif %}

-

Abstract{% if version.abstract and version.abstract != version.preprint.abstract %} (changed){% endif %}

-

{% if version.abstract == version.preprint.abstract %}Unchanged{% else %}{{ version.abstract|safe|linebreaksbr }}{% endif %}

+ {% with version.translated_metadata as new_meta %} + {% if new_meta %} + {% for meta in new_meta %} +

Title ({{ meta.language }})

+

{{ meta.title|default:"Unchanged" }}

+

Abstract ({{ meta.language }})

+

{{ meta.abstract|default:"Unchanged"|safe|linebreaksbr }}

+ {% endfor %} + {% else %} +

Title{% if version.title and version.title != version.preprint.title %} (changed){% endif %}

+

{% if version.title == version.preprint.title %}Unchanged{% else %} {{ version.title }}{% endif %}

+

Abstract{% if version.abstract and version.abstract != version.preprint.abstract %} (changed){% endif %}

+

{% if version.abstract == version.preprint.abstract %}Unchanged{% else %}{{ version.abstract|safe|linebreaksbr }}{% endif %}

+ {% endif %} + {% endwith %}

Published DOI{% if version.published_doi and version.published_doi != version.preprint.doi %} (changed){% endif %}

{% if version.published_doi == version.preprint.doi %}Unchanged{% else %}{{ version.published_doi }}{% endif %}

diff --git a/src/templates/admin/press/nav.html b/src/templates/admin/press/nav.html index ff848295a5..8d43c35ba3 100644 --- a/src/templates/admin/press/nav.html +++ b/src/templates/admin/press/nav.html @@ -19,11 +19,12 @@ @@ -41,6 +42,7 @@ {% if request.user.is_staff %}
  •  Subjects
  •  Licences
  • +
  •  Languages
  •  Additional Fields
  •  Recommendations
  •  Settings
  • diff --git a/src/templates/admin/repository/article.html b/src/templates/admin/repository/article.html index a686861fc3..62fcb1c50b 100644 --- a/src/templates/admin/repository/article.html +++ b/src/templates/admin/repository/article.html @@ -26,24 +26,26 @@

    Metadata

    - + - + + + - + - - - - + - + diff --git a/src/templates/admin/repository/author_article.html b/src/templates/admin/repository/author_article.html index ee4007c5c0..6fac1ef594 100644 --- a/src/templates/admin/repository/author_article.html +++ b/src/templates/admin/repository/author_article.html @@ -61,10 +61,12 @@

    Metadata

    - + + - + + diff --git a/src/templates/admin/repository/languages.html b/src/templates/admin/repository/languages.html new file mode 100644 index 0000000000..f55e5fe99d --- /dev/null +++ b/src/templates/admin/repository/languages.html @@ -0,0 +1,103 @@ +{% extends "admin/core/base.html" %} +{% load static %} +{% load foundation %} +{% load bool_fa %} + +{% block title %}{{ request.repository.name }} Languages{% endblock %} +{% block title-section %}{{ request.repository.name }} Languages{% endblock %} +{% block title-sub %}Management interface for {{ request.repository.name }} Languages{% endblock %} + +{% block breadcrumbs %} +
  • Press Manager
  • +
  • Preprint Manager
  • +
  • Languages
  • +{% endblock %} + +{% block body %} +
    +
    +
    +
    +

    Active Languages

    +
    +
    +

    Languages enabled for this repository. Authors will be able to provide title and abstract translations in each active language.

    +
    + {% csrf_token %} +
    TitleTitle
    {{ preprint.title }}{{ preprint.title }}
    Owner Licence TypeLanguage
    {{ preprint.owner.full_name }} {{ preprint.license.short_name }} {{ preprint.submission_type.name|default:"No submission type set" }}{{ preprint.language_name|default:"—" }}
    Preprint DOIPublished DOIPublished DOI
    @@ -53,7 +55,7 @@

    Metadata

    pending {% endif %}
    {% if preprint.doi %} + {% if preprint.doi %} {% include "common/elements/doi_display.html" with doi=preprint.doi title=preprint.title %} {% else %} No Published DOI @@ -61,22 +63,22 @@

    Metadata

    + Subjects
    + {% include "common/repository/subject_display.html" %}
    StartedStarted Submitted
    {{ preprint.date_started }}{{ preprint.date_started }} {{ preprint.date_submitted }}
    LicenseLicenseLanguage
    {{ preprint.license }}{{ preprint.license }}{{ preprint.language_name|default:"—" }}
    Comments to the Editor
    + + + + + + + + + + {% for code, name in active_language_details %} + + + + + + + {% endfor %} + +
    LanguageCodeDefault
    {{ name }}{{ code }} + {% if code == request.repository.default_language %} + Current Default + {% else %} + + {% endif %} + + +
    + +
    +
    + +
    +
    +
    +

    All Languages

    +
    +
    +

    Enable additional languages for this repository.

    +
    + {% csrf_token %} + + + + + + + + + + + {% for code, name in language_choices %} + + + + + + + {% endfor %} + +
    LanguageCodeEnabledEnable/Disable
    {{ name }}{{ code }}{% if code in active_languages %}{{ True|bool_fa }}{% else %}{{ False|bool_fa }}{% endif %} + {% if code in active_languages %} + + {% else %} + + {% endif %} +
    +
    +
    +
    +
    + +{% endblock %} + +{% block js %} + {% include "admin/elements/datatables.html" with target="#languages" page_length=25 sort=0 order="asc" %} +{% endblock %} diff --git a/src/templates/admin/repository/submit/info.html b/src/templates/admin/repository/submit/info.html index f8bbc42d9f..3afeb5043d 100644 --- a/src/templates/admin/repository/submit/info.html +++ b/src/templates/admin/repository/submit/info.html @@ -24,15 +24,31 @@

    {% trans "Metadata" %}

    -
    - {{ form.title|foundation }} -
    + {% if form.language_field_names %} +
    +
    +

    {% trans "This repository accepts submissions in multiple languages. Please provide a title and abstract in the primary language. Translations are optional." %}

    +
    +
    + {% for field_name in form.language_field_names %} + {% get_form_field form field_name as lang_field %} +
    + {{ lang_field|foundation }} +
    + {% endfor %} + {% else %} +
    + {{ form.title|foundation }} +
    +
    + {{ form.abstract|foundation }} +
    + {% endif %}
    {{ form.submission_type|foundation }}
    - {{ form.abstract|foundation }} - + {{ form.language|foundation }}
    {{ form.license|foundation }} diff --git a/src/templates/admin/repository/submit_update.html b/src/templates/admin/repository/submit_update.html index ce93db83a1..258d1d08ed 100644 --- a/src/templates/admin/repository/submit_update.html +++ b/src/templates/admin/repository/submit_update.html @@ -1,6 +1,7 @@ {% extends "admin/core/base.html" %} {% load i18n %} {% load foundation %} +{% load field %} {% block title %}{{ preprint.title|striptags }}{% endblock %} {% block title-section %}{{ request.repository.object_name }} #{{ preprint.pk }} - {{ preprint.title|safe }}{% endblock %} @@ -25,7 +26,15 @@

    Update Preprint

    {% csrf_token %} {% include "admin/elements/forms/errors.html" with form=version_form %} {% include "admin/elements/forms/errors.html" with form=file_form %} - {{ version_form|foundation }} + {% if version_form.language_field_names %} + {% for field_name in version_form.language_field_names %} + {% get_form_field version_form field_name as lang_field %} + {{ lang_field|foundation }} + {% endfor %} + {{ version_form.published_doi|foundation }} + {% else %} + {{ version_form|foundation }} + {% endif %} {% if file_form %} {{ file_form|foundation }} {% endif %} diff --git a/src/templates/admin/repository/wizard.html b/src/templates/admin/repository/wizard.html index ea39c58f5c..332a432e4a 100644 --- a/src/templates/admin/repository/wizard.html +++ b/src/templates/admin/repository/wizard.html @@ -43,6 +43,16 @@

    {% csrf_token %} {{ form|foundation }} + {% if step == '3' and repository %} +
    +

    Languages

    +

    + Active languages: + {% for lang in repository_active_language_names %}{{ lang }}{% if not forloop.last %}, {% endif %}{% endfor %}{% if not repository_active_language_names %}English{% endif %}. +

    + Manage Languages +
    + {% endif %}
    diff --git a/src/templates/common/metadata/preprint_citation.html b/src/templates/common/metadata/preprint_citation.html index 71cfdb0850..0b71335bfb 100644 --- a/src/templates/common/metadata/preprint_citation.html +++ b/src/templates/common/metadata/preprint_citation.html @@ -2,4 +2,5 @@ {% endfor %} {% if preprint.date_published %}{% endif %} {% if preprint.current_version_file_type == 'pdf' %}{% endif %} - \ No newline at end of file +{% if preprint.language %}{% endif %} + \ No newline at end of file diff --git a/src/templates/common/metadata/preprint_dc.html b/src/templates/common/metadata/preprint_dc.html index 10a8a11cb1..2040502a35 100644 --- a/src/templates/common/metadata/preprint_dc.html +++ b/src/templates/common/metadata/preprint_dc.html @@ -2,13 +2,14 @@ {% if preprint.date_submitted %}{% endif %} {% if preprint.date_published %}{% endif %} {% if preprint.current_version %}{% endif %} - + {% if preprint.current_version_file_type == 'pdf' %}{% elif preprint.current_version_file_type == 'html' %}{% endif %} + {% if preprint.language %}{% endif %} - + {% for author in preprint.authors %} {% endfor %} diff --git a/src/templates/elements/repository/language_switcher.html b/src/templates/elements/repository/language_switcher.html new file mode 100644 index 0000000000..575839686c --- /dev/null +++ b/src/templates/elements/repository/language_switcher.html @@ -0,0 +1,19 @@ +{% load i18n %} + +{% if request.repository.languages|length > 1 %} + {% get_current_language as LANGUAGE_CODE %} + {% get_available_languages as LANGUAGES %} + {% csrf_token %} + + + + +{% endif %} diff --git a/src/templates/elements/repository/language_switcher_buttons.html b/src/templates/elements/repository/language_switcher_buttons.html new file mode 100644 index 0000000000..9a3046a706 --- /dev/null +++ b/src/templates/elements/repository/language_switcher_buttons.html @@ -0,0 +1,16 @@ +{% load i18n %} + +{% if request.repository.languages|length > 1 %} + {% get_current_language as LANGUAGE_CODE %} + {% get_available_languages as LANGUAGES %} + {% for lang_code, lang_name in LANGUAGES %} + {% if lang_code in request.available_languages %} +
  • +
    {% csrf_token %} + + +
    +
  • + {% endif %} + {% endfor %} +{% endif %} diff --git a/src/themes/OLH/templates/core/base.html b/src/themes/OLH/templates/core/base.html index 73a34d0106..f177a92185 100644 --- a/src/themes/OLH/templates/core/base.html +++ b/src/themes/OLH/templates/core/base.html @@ -131,6 +131,12 @@
    + {% elif request.repository.languages|length > 1 %} +
    + +
    {% endif %}