diff --git a/app/admin.py b/app/admin.py index c3dedf8a..a641c949 100644 --- a/app/admin.py +++ b/app/admin.py @@ -21,6 +21,7 @@ from django import forms from codemirror2.widgets import CodeMirrorEditor from webodm import settings from django.core.files.uploadedfile import InMemoryUploadedFile +from django.utils.translation import gettext_lazy as _ admin.site.register(Project, GuardedModelAdmin) @@ -48,21 +49,24 @@ admin.site.register(Setting, SettingAdmin) class ThemeModelForm(forms.ModelForm): - css = forms.CharField(help_text="Enter custom CSS", + css = forms.CharField(help_text=_("Enter custom CSS"), + label=_("CSS"), required=False, widget=CodeMirrorEditor(options={'mode': 'css', 'lineNumbers': True})) - html_before_header = forms.CharField(help_text="HTML that will be displayed above site header", + html_before_header = forms.CharField(help_text=_("HTML that will be displayed above site header"), + label=_("HTML (before header)"), required=False, widget=CodeMirrorEditor(options={'mode': 'xml', 'lineNumbers': True})) - html_after_header = forms.CharField(help_text="HTML that will be displayed after site header", + html_after_header = forms.CharField(help_text=_("HTML that will be displayed after site header"), + label=_("HTML (after header)"), required=False, widget=CodeMirrorEditor(options={'mode': 'xml', 'lineNumbers': True})) - html_after_body = forms.CharField(help_text="HTML that will be displayed after the </body> tag", + html_after_body = forms.CharField(help_text=_("HTML that will be displayed after the </body> tag"), + label=_("HTML (after body)"), required=False, widget=CodeMirrorEditor(options={'mode': 'xml', 'lineNumbers': True})) - html_footer = forms.CharField(help_text="HTML that will be displayed in the footer. You can also use the special tags:" - "

{ORGANIZATION}: show a link to your organization.

" - "

{YEAR}: show current year

", + html_footer = forms.CharField(help_text=_("HTML that will be displayed in the footer. You can also use the special tags such as {ORGANIZATION} and {YEAR}."), + label=_("HTML (footer)"), required=False, widget=CodeMirrorEditor(options={'mode': 'xml', 'lineNumbers': True})) diff --git a/app/models/image_upload.py b/app/models/image_upload.py index d5f0f924..8ccbdb79 100644 --- a/app/models/image_upload.py +++ b/app/models/image_upload.py @@ -1,16 +1,21 @@ from .task import Task, assets_directory_path from django.db import models +from django.utils.translation import gettext_lazy as _ def image_directory_path(image_upload, filename): return assets_directory_path(image_upload.task.id, image_upload.task.project.id, filename) class ImageUpload(models.Model): - task = models.ForeignKey(Task, on_delete=models.CASCADE, help_text="Task this image belongs to") - image = models.ImageField(upload_to=image_directory_path, help_text="File uploaded by a user", max_length=512) + task = models.ForeignKey(Task, on_delete=models.CASCADE, help_text=_("Task this image belongs to"), verbose_name=_("Task")) + image = models.ImageField(upload_to=image_directory_path, help_text=_("File uploaded by a user"), max_length=512, verbose_name=_("Image")) def __str__(self): return self.image.name def path(self): return self.image.path + + class Meta: + verbose_name = _("Image Upload") + verbose_name_plural = _("Image Uploads") \ No newline at end of file diff --git a/app/models/plugin.py b/app/models/plugin.py index 7047369b..bd6847e1 100644 --- a/app/models/plugin.py +++ b/app/models/plugin.py @@ -1,8 +1,9 @@ from django.db import models +from django.utils.translation import gettext_lazy as _ class Plugin(models.Model): - name = models.CharField(max_length=255, primary_key=True, blank=False, null=False, help_text="Plugin name") - enabled = models.BooleanField(db_index=True, default=True, help_text="Whether this plugin is enabled.") + name = models.CharField(max_length=255, primary_key=True, blank=False, null=False, help_text=_("Plugin name"), verbose_name=_("Name")) + enabled = models.BooleanField(db_index=True, default=True, help_text=_("Whether this plugin is enabled."), verbose_name="Enabled") def __str__(self): return self.name @@ -13,4 +14,8 @@ class Plugin(models.Model): def disable(self): self.enabled = False - self.save() \ No newline at end of file + self.save() + + class Meta: + verbose_name = _("Plugin") + verbose_name_plural = _("Plugins") \ No newline at end of file diff --git a/app/models/plugin_datum.py b/app/models/plugin_datum.py index 22e2b443..d99b1a63 100644 --- a/app/models/plugin_datum.py +++ b/app/models/plugin_datum.py @@ -1,18 +1,20 @@ -import logging from django.db import models from django.contrib.postgres import fields from django.conf import settings - -logger = logging.getLogger('app.logger') +from django.utils.translation import gettext_lazy as _ class PluginDatum(models.Model): - key = models.CharField(max_length=255, help_text="Setting key", db_index=True) - user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, default=None, on_delete=models.CASCADE, help_text="The user this setting belongs to. If NULL, the setting is global.") - int_value = models.IntegerField(blank=True, null=True, default=None, help_text="Integer value") - float_value = models.FloatField(blank=True, null=True, default=None, help_text="Float value") - bool_value = models.NullBooleanField(blank=True, null=True, default=None, help_text="Bool value") - string_value = models.TextField(blank=True, null=True, default=None, help_text="String value") - json_value = fields.JSONField(default=None, blank=True, null=True, help_text="JSON value") + key = models.CharField(max_length=255, help_text=_("Setting key"), db_index=True, verbose_name=_("Key")) + user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, default=None, on_delete=models.CASCADE, help_text=_("The user this setting belongs to. If NULL, the setting is global."), verbose_name=_("User")) + int_value = models.IntegerField(blank=True, null=True, default=None, verbose_name=_("Integer value")) + float_value = models.FloatField(blank=True, null=True, default=None, verbose_name=_("Float value")) + bool_value = models.NullBooleanField(blank=True, null=True, default=None, verbose_name=_("Bool value")) + string_value = models.TextField(blank=True, null=True, default=None, verbose_name=_("String value")) + json_value = fields.JSONField(default=None, blank=True, null=True, verbose_name=_("JSON value")) def __str__(self): return self.key + + class Meta: + verbose_name = _("Plugin Datum") + verbose_name_plural = _("Plugin Datum") \ No newline at end of file diff --git a/app/models/preset.py b/app/models/preset.py index ea5fcbf7..5e6612b1 100644 --- a/app/models/preset.py +++ b/app/models/preset.py @@ -1,21 +1,21 @@ -import logging - from django.conf import settings from django.contrib.postgres.fields import JSONField from django.db import models from django.utils import timezone from .task import validate_task_options - -logger = logging.getLogger('app.logger') - +from django.utils.translation import gettext_lazy as _ class Preset(models.Model): - owner = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.CASCADE, help_text="The person who owns this preset") - name = models.CharField(max_length=255, blank=False, null=False, help_text="A label used to describe the preset") - options = JSONField(default=list, blank=True, help_text="Options that define this preset (same format as in a Task's options).", + owner = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.CASCADE, help_text=_("The person who owns this preset"), verbose_name=_("Owner")) + name = models.CharField(max_length=255, blank=False, null=False, help_text=_("A label used to describe the preset"), verbose_name=_("Name")) + options = JSONField(default=list, blank=True, help_text=_("Options that define this preset (same format as in a Task's options)."), verbose_name=_("Options"), validators=[validate_task_options]) - created_at = models.DateTimeField(default=timezone.now, help_text="Creation date") - system = models.BooleanField(db_index=True, default=False, help_text="Whether this preset is available to every user in the system or just to its owner.") + created_at = models.DateTimeField(default=timezone.now, help_text=_("Creation date"), verbose_name=_("Created at")) + system = models.BooleanField(db_index=True, default=False, help_text=_("Whether this preset is available to every user in the system or just to its owner."), verbose_name=_("System")) def __str__(self): return self.name + + class Meta: + verbose_name = _("Preset") + verbose_name_plural = _("Presets") diff --git a/app/models/project.py b/app/models/project.py index f8b6944f..7fb879ba 100644 --- a/app/models/project.py +++ b/app/models/project.py @@ -9,6 +9,7 @@ from django.utils import timezone from guardian.models import GroupObjectPermissionBase from guardian.models import UserObjectPermissionBase from guardian.shortcuts import get_perms_for_model, assign_perm +from django.utils.translation import gettext_lazy as _ from app import pending_actions @@ -18,11 +19,11 @@ logger = logging.getLogger('app.logger') class Project(models.Model): - owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, help_text="The person who created the project") - name = models.CharField(max_length=255, help_text="A label used to describe the project") - description = models.TextField(default="", blank=True, help_text="More in-depth description of the project") - created_at = models.DateTimeField(default=timezone.now, help_text="Creation date") - deleting = models.BooleanField(db_index=True, default=False, help_text="Whether this project has been marked for deletion. Projects that have running tasks need to wait for tasks to be properly cleaned up before they can be deleted.") + owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, help_text=_("The person who created the project"), verbose_name=_("Owner")) + name = models.CharField(max_length=255, help_text=_("A label used to describe the project"), verbose_name=_("Name")) + description = models.TextField(default="", blank=True, help_text=_("More in-depth description of the project"), verbose_name=_("Description")) + created_at = models.DateTimeField(default=timezone.now, help_text=_("Creation date"), verbose_name=_("Created at")) + deleting = models.BooleanField(db_index=True, default=False, help_text=_("Whether this project has been marked for deletion. Projects that have running tasks need to wait for tasks to be properly cleaned up before they can be deleted."), verbose_name=_("Deleting")) def delete(self, *args): # No tasks? @@ -51,6 +52,9 @@ class Project(models.Model): ).filter(Q(orthophoto_extent__isnull=False) | Q(dsm_extent__isnull=False) | Q(dtm_extent__isnull=False)) .only('id', 'project_id')] + class Meta: + verbose_name = _("Project") + verbose_name_plural = _("Projects") @receiver(signals.post_save, sender=Project, dispatch_uid="project_post_save") def project_post_save(sender, instance, created, **kwargs): diff --git a/app/models/setting.py b/app/models/setting.py index 9f1537fd..17236200 100644 --- a/app/models/setting.py +++ b/app/models/setting.py @@ -8,6 +8,8 @@ from django.db.models import signals from django.dispatch import receiver from imagekit.models import ImageSpecField from imagekit.processors import ResizeToFit +from django.utils.translation import gettext +from django.utils.translation import gettext_lazy as _ from webodm import settings from .theme import Theme, update_theme_css @@ -16,8 +18,8 @@ logger = logging.getLogger('app.logger') class Setting(models.Model): - app_name = models.CharField(max_length=255, blank=False, null=False, help_text="The name of your application") - app_logo = models.ImageField(upload_to="settings/", blank=False, null=False, help_text="A 512x512 logo of your application (.png or .jpeg)") + app_name = models.CharField(max_length=255, blank=False, null=False, help_text=_("The name of your application"), verbose_name=_("App name")) + app_logo = models.ImageField(upload_to="settings/", blank=False, null=False, help_text=_("A 512x512 logo of your application (.png or .jpeg)"), verbose_name=_("App logo")) app_logo_36 = ImageSpecField(source='app_logo', processors=[ResizeToFit(36, 36)], format='PNG', @@ -27,10 +29,10 @@ class Setting(models.Model): format='PNG', options={'quality': 90}) - organization_name = models.CharField(default='WebODM', max_length=255, blank=True, null=True, help_text="The name of your organization") - organization_website = models.URLField(default='https://github.com/OpenDroneMap/WebODM/', max_length=255, blank=True, null=True, help_text="The website URL of your organization") - theme = models.ForeignKey(Theme, blank=False, null=False, on_delete=models.DO_NOTHING, - help_text="Active theme") + organization_name = models.CharField(default='WebODM', max_length=255, blank=True, null=True, help_text=_("The name of your organization"), verbose_name=_("Organization name")) + organization_website = models.URLField(default='https://github.com/OpenDroneMap/WebODM/', max_length=255, blank=True, null=True, help_text=_("The website URL of your organization"), verbose_name=_("Organization website")) + theme = models.ForeignKey(Theme, blank=False, null=False, on_delete=models.DO_NOTHING, verbose_name=_("Theme"), + help_text=_("Active theme")) def __init__(self, *args, **kwargs): super(Setting, self).__init__(*args, **kwargs) @@ -69,7 +71,11 @@ class Setting(models.Model): super(Setting, self).save(*args, **kwargs) def __str__(self): - return "Application" + return gettext("Application") + + class Meta: + verbose_name = _("Settings") + verbose_name_plural = _("Settings") @receiver(signals.pre_save, sender=Setting, dispatch_uid="setting_pre_save") diff --git a/app/models/task.py b/app/models/task.py index abbd8004..e02e3f74 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -37,6 +37,7 @@ from pyodm.exceptions import NodeResponseError, NodeConnectionError, NodeServerE from webodm import settings from app.classes.gcp import GCPFile from .project import Project +from django.utils.translation import gettext_lazy as _, gettext from functools import partial import subprocess @@ -213,43 +214,50 @@ class Task(models.Model): TASK_PROGRESS_LAST_VALUE = 0.85 - id = models.UUIDField(primary_key=True, default=uuid_module.uuid4, unique=True, serialize=False, editable=False) + id = models.UUIDField(primary_key=True, default=uuid_module.uuid4, unique=True, serialize=False, editable=False, verbose_name=_("Id")) - uuid = models.CharField(max_length=255, db_index=True, default='', blank=True, help_text="Identifier of the task (as returned by OpenDroneMap's REST API)") - project = models.ForeignKey(Project, on_delete=models.CASCADE, help_text="Project that this task belongs to") - name = models.CharField(max_length=255, null=True, blank=True, help_text="A label for the task") - processing_time = models.IntegerField(default=-1, help_text="Number of milliseconds that elapsed since the beginning of this task (-1 indicates that no information is available)") - processing_node = models.ForeignKey(ProcessingNode, on_delete=models.SET_NULL, null=True, blank=True, help_text="Processing node assigned to this task (or null if this task has not been associated yet)") - auto_processing_node = models.BooleanField(default=True, help_text="A flag indicating whether this task should be automatically assigned a processing node") - status = models.IntegerField(choices=STATUS_CODES, db_index=True, null=True, blank=True, help_text="Current status of the task") - last_error = models.TextField(null=True, blank=True, help_text="The last processing error received") - options = fields.JSONField(default=dict, blank=True, help_text="Options that are being used to process this task", validators=[validate_task_options]) - available_assets = fields.ArrayField(models.CharField(max_length=80), default=list, blank=True, help_text="List of available assets to download") - console_output = models.TextField(null=False, default="", blank=True, help_text="Console output of the OpenDroneMap's process") + uuid = models.CharField(max_length=255, db_index=True, default='', blank=True, help_text=_("Identifier of the task (as returned by NodeODM API)"), verbose_name=_("UUID")) + project = models.ForeignKey(Project, on_delete=models.CASCADE, help_text=_("Project that this task belongs to"), verbose_name=_("Project")) + name = models.CharField(max_length=255, null=True, blank=True, help_text=_("A label for the task"), verbose_name=_("Name")) + processing_time = models.IntegerField(default=-1, help_text=_("Number of milliseconds that elapsed since the beginning of this task (-1 indicates that no information is available)"), verbose_name=_("Processing Time")) + processing_node = models.ForeignKey(ProcessingNode, on_delete=models.SET_NULL, null=True, blank=True, help_text=_("Processing node assigned to this task (or null if this task has not been associated yet)"), verbose_name=_("Processing Node")) + auto_processing_node = models.BooleanField(default=True, help_text=_("A flag indicating whether this task should be automatically assigned a processing node"), verbose_name=_("Auto Processing Node")) + status = models.IntegerField(choices=STATUS_CODES, db_index=True, null=True, blank=True, help_text=_("Current status of the task"), verbose_name=_("Status")) + last_error = models.TextField(null=True, blank=True, help_text=_("The last processing error received"), verbose_name=_("Last Error")) + options = fields.JSONField(default=dict, blank=True, help_text=_("Options that are being used to process this task"), validators=[validate_task_options], verbose_name=_("Options")) + available_assets = fields.ArrayField(models.CharField(max_length=80), default=list, blank=True, help_text=_("List of available assets to download"), verbose_name=_("Available Assets")) + console_output = models.TextField(null=False, default="", blank=True, help_text=_("Console output of the processing node"), verbose_name=_("Console Output")) - orthophoto_extent = GeometryField(null=True, blank=True, srid=4326, help_text="Extent of the orthophoto created by OpenDroneMap") - dsm_extent = GeometryField(null=True, blank=True, srid=4326, help_text="Extent of the DSM created by OpenDroneMap") - dtm_extent = GeometryField(null=True, blank=True, srid=4326, help_text="Extent of the DTM created by OpenDroneMap") + orthophoto_extent = GeometryField(null=True, blank=True, srid=4326, help_text=_("Extent of the orthophoto"), verbose_name=_("Orthophoto Extent")) + dsm_extent = GeometryField(null=True, blank=True, srid=4326, help_text="Extent of the DSM", verbose_name=_("DSM Extent")) + dtm_extent = GeometryField(null=True, blank=True, srid=4326, help_text="Extent of the DTM", verbose_name=_("DTM Extent")) # mission - created_at = models.DateTimeField(default=timezone.now, help_text="Creation date") - pending_action = models.IntegerField(choices=PENDING_ACTIONS, db_index=True, null=True, blank=True, help_text="A requested action to be performed on the task. The selected action will be performed by the worker at the next iteration.") + created_at = models.DateTimeField(default=timezone.now, help_text=_("Creation date"), verbose_name=_("Created at")) + pending_action = models.IntegerField(choices=PENDING_ACTIONS, db_index=True, null=True, blank=True, help_text=_("A requested action to be performed on the task. The selected action will be performed by the worker at the next iteration."), verbose_name=_("Pending Action")) - public = models.BooleanField(default=False, help_text="A flag indicating whether this task is available to the public") - resize_to = models.IntegerField(default=-1, help_text="When set to a value different than -1, indicates that the images for this task have been / will be resized to the size specified here before processing.") + public = models.BooleanField(default=False, help_text=_("A flag indicating whether this task is available to the public"), verbose_name=_("Public")) + resize_to = models.IntegerField(default=-1, help_text=_("When set to a value different than -1, indicates that the images for this task have been / will be resized to the size specified here before processing."), verbose_name=_("Resize To")) upload_progress = models.FloatField(default=0.0, - help_text="Value between 0 and 1 indicating the upload progress of this task's files to the processing node", + help_text=_("Value between 0 and 1 indicating the upload progress of this task's files to the processing node"), + verbose_name=_("Upload Progress"), blank=True) resize_progress = models.FloatField(default=0.0, - help_text="Value between 0 and 1 indicating the resize progress of this task's images", + help_text=_("Value between 0 and 1 indicating the resize progress of this task's images"), + verbose_name=_("Resize Progress"), blank=True) running_progress = models.FloatField(default=0.0, - help_text="Value between 0 and 1 indicating the running progress (estimated) of this task", + help_text=_("Value between 0 and 1 indicating the running progress (estimated) of this task"), + verbose_name=_("Running Progress"), blank=True) - import_url = models.TextField(null=False, default="", blank=True, help_text="URL this task is imported from (only for imported tasks)") - images_count = models.IntegerField(null=False, blank=True, default=0, help_text="Number of images associated with this task") - partial = models.BooleanField(default=False, help_text="A flag indicating whether this task is currently waiting for information or files to be uploaded before being considered for processing.") + import_url = models.TextField(null=False, default="", blank=True, help_text=_("URL this task is imported from (only for imported tasks)"), verbose_name=_("Import URL")) + images_count = models.IntegerField(null=False, blank=True, default=0, help_text=_("Number of images associated with this task"), verbose_name=_("Images Count")) + partial = models.BooleanField(default=False, help_text=_("A flag indicating whether this task is currently waiting for information or files to be uploaded before being considered for processing."), verbose_name=_("Partial")) + + class Meta: + verbose_name = _("Task") + verbose_name_plural = _("Tasks") def __init__(self, *args, **kwargs): super(Task, self).__init__(*args, **kwargs) @@ -258,7 +266,7 @@ class Task(models.Model): self.__original_project_id = self.project.id def __str__(self): - name = self.name if self.name is not None else "unnamed" + name = self.name if self.name is not None else gettext("unnamed") return 'Task [{}] ({})'.format(name, self.id) @@ -361,7 +369,7 @@ class Task(models.Model): raise FileNotFoundError("{} is not a valid asset".format(asset)) def handle_import(self): - self.console_output += "Importing assets...\n" + self.console_output += gettext("Importing assets...") + "\n" self.save() zip_path = self.assets_path("all.zip") @@ -400,7 +408,7 @@ class Task(models.Model): try: self.extract_assets_and_complete() except zipfile.BadZipFile: - raise NodeServerError("Invalid zip file") + raise NodeServerError(gettext("Invalid zip file")) images_json = self.assets_path("images.json") if os.path.exists(images_json): @@ -494,7 +502,7 @@ class Task(models.Model): except NodeConnectionError as e: # If we can't create a task because the node is offline # We want to fail instead of trying again - raise NodeServerError('Connection error: ' + str(e)) + raise NodeServerError(gettext('Connection error: %(error)s') % {'error': str(e)}) # Refresh task object before committing change self.refresh_from_db() @@ -574,7 +582,7 @@ class Task(models.Model): self.running_progress = 0 self.save() else: - raise NodeServerError("Cannot restart a task that has no processing node") + raise NodeServerError(gettext("Cannot restart a task that has no processing node")) elif self.pending_action == pending_actions.REMOVE: logger.info("Removing {}".format(self)) @@ -668,7 +676,7 @@ class Task(models.Model): retry_num += 1 os.remove(all_zip_path) else: - raise NodeServerError("Invalid zip file") + raise NodeServerError(gettext("Invalid zip file")) else: # FAILED, CANCELED self.save() @@ -726,7 +734,7 @@ class Task(models.Model): with connection.cursor() as cursor: cursor.execute("SELECT SRID FROM spatial_ref_sys WHERE SRID = %s", [raster.srid]) if cursor.rowcount == 0: - raise NodeServerError("Unsupported SRS {}. Please make sure you picked a supported SRS.".format(raster.srid)) + raise NodeServerError(gettext("Unsupported SRS %(code)s. Please make sure you picked a supported SRS.") % {'code': str(raster.srid)}) # It will be implicitly transformed into the SRID of the model’s field # self.field = GEOSGeometry(...) @@ -736,7 +744,7 @@ class Task(models.Model): self.update_available_assets_field() self.running_progress = 1.0 - self.console_output += "Done!\n" + self.console_output += gettext("Done!") + "\n" self.status = status_codes.COMPLETED self.save() diff --git a/app/models/theme.py b/app/models/theme.py index 10abbf73..232dc42b 100644 --- a/app/models/theme.py +++ b/app/models/theme.py @@ -6,43 +6,47 @@ from django.db.models import signals from django.db import models from colorfield.fields import ColorField from django.dispatch import receiver +from django.utils.translation import gettext_lazy as _ from webodm import settings logger = logging.getLogger('app.logger') class Theme(models.Model): - name = models.CharField(max_length=255, blank=False, null=False, help_text="Name of theme") + name = models.CharField(max_length=255, blank=False, null=False, help_text=_("Name of theme"), verbose_name=_("Name")) # Similar to how discourse.org does it - primary = ColorField(default='#2c3e50', help_text="Most text, icons, and borders.") - secondary = ColorField(default='#ffffff', help_text="The main background color, and text color of some buttons.") - tertiary = ColorField(default='#3498db', help_text="Navigation links.") + primary = ColorField(default='#2c3e50', help_text=_("Most text, icons, and borders."), verbose_name=_("Primary")) + secondary = ColorField(default='#ffffff', help_text=_("The main background color, and text color of some buttons."), verbose_name=_("Secondary")) + tertiary = ColorField(default='#3498db', help_text=_("Navigation links."), verbose_name=_("Tertiary")) - button_primary = ColorField(default='#2c3e50', help_text="Primary button color.") - button_default = ColorField(default='#95a5a6', help_text="Default button color.") - button_danger = ColorField(default='#e74c3c', help_text="Delete button color.") + button_primary = ColorField(default='#2c3e50', help_text=_("Primary button color."), verbose_name=_("Button Primary")) + button_default = ColorField(default='#95a5a6', help_text=_("Default button color."), verbose_name=_("Button Default")) + button_danger = ColorField(default='#e74c3c', help_text=_("Delete button color."), verbose_name=_("Button Danger")) - header_background = ColorField(default='#3498db', help_text="Background color of the site's header.") - header_primary = ColorField(default='#ffffff', help_text="Text and icons in the site's header.") + header_background = ColorField(default='#3498db', help_text=_("Background color of the site's header."), verbose_name=_("Header Background")) + header_primary = ColorField(default='#ffffff', help_text=_("Text and icons in the site's header."), verbose_name=_("Header Primary")) - border = ColorField(default='#e7e7e7', help_text="The color of most borders.") - highlight = ColorField(default='#f7f7f7', help_text="The background color of panels and some borders.") + border = ColorField(default='#e7e7e7', help_text=_("The color of most borders."), verbose_name=_("Border")) + highlight = ColorField(default='#f7f7f7', help_text=_("The background color of panels and some borders."), verbose_name=_("Highlight")) - dialog_warning = ColorField(default='#f39c12', help_text="The border color of warning dialogs.") + dialog_warning = ColorField(default='#f39c12', help_text=_("The border color of warning dialogs."), verbose_name=_("Dialog Warning")) - failed = ColorField(default='#ffcbcb', help_text="The background color of failed notifications.") - success = ColorField(default='#cbffcd', help_text="The background color of success notifications.") + failed = ColorField(default='#ffcbcb', help_text=_("The background color of failed notifications."), verbose_name=_("Failed")) + success = ColorField(default='#cbffcd', help_text=_("The background color of success notifications."), verbose_name=_("Success")) - css = models.TextField(default='', blank=True) - html_before_header = models.TextField(default='', blank=True) - html_after_header = models.TextField(default='', blank=True) - html_after_body = models.TextField(default='', blank=True) - html_footer = models.TextField(default='', blank=True) + css = models.TextField(default='', blank=True, verbose_name=_("CSS")) + html_before_header = models.TextField(default='', blank=True, verbose_name=_("HTML (before header)")) + html_after_header = models.TextField(default='', blank=True, verbose_name=_("HTML (after header)")) + html_after_body = models.TextField(default='', blank=True, verbose_name=_("HTML (after body)")) + html_footer = models.TextField(default='', blank=True, verbose_name=_("HTML (footer)")) def __str__(self): return self.name + class Meta: + verbose_name = _("Theme") + verbose_name_plural = _("Theme") @receiver(signals.post_save, sender=Theme, dispatch_uid="theme_post_save") def theme_post_save(sender, instance, created, **kwargs): diff --git a/app/static/app/js/ModelView.jsx b/app/static/app/js/ModelView.jsx index b88ac3d8..73a36127 100644 --- a/app/static/app/js/ModelView.jsx +++ b/app/static/app/js/ModelView.jsx @@ -496,6 +496,19 @@ $(function(){ } window.proj4.defs(defs); + // Use gettext for translations + const oldInit = i18n.init; + i18n.addPostProcessor("gettext", function(v, k, opts){ + if (v){ + return _(v); + }else return v; + }); + i18n.init = function(opts, cb){ + opts.preload = ['en']; + opts.postProcess = "gettext"; + oldInit(opts, cb); + }; + $("[data-modelview]").each(function(){ let props = $(this).data(); delete(props.modelview); diff --git a/app/templates/app/admin/500.html b/app/templates/app/admin/500.html index 4842faa6..492959c3 100644 --- a/app/templates/app/admin/500.html +++ b/app/templates/app/admin/500.html @@ -11,7 +11,7 @@ {% block title %}{% trans 'Server error (500)' %}{% endblock %} {% block content %} -

{% trans 'Server Error (500)' %}

+

{% trans 'Server Error (500)' %}

{% trans "There's been an error. It's been reported to the site administrators via email and should be fixed shortly. Thanks for your patience." %}

{% endblock %} diff --git a/app/templates/app/admin/change_list_plugin.html b/app/templates/app/admin/change_list_plugin.html index 2cc5ce78..f22a2205 100644 --- a/app/templates/app/admin/change_list_plugin.html +++ b/app/templates/app/admin/change_list_plugin.html @@ -1,4 +1,5 @@ {% extends "admin/change_list.html" %} +{% load i18n %} {% block content_title %}