Manifest plugin translations

pull/943/head
Piero Toffanin 2020-12-21 14:18:43 -05:00
rodzic 34b24ae1be
commit 19e809d49b
9 zmienionych plików z 184 dodań i 30 usunięć

Wyświetl plik

@ -21,7 +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 _
from django.utils.translation import gettext_lazy as _, gettext
admin.site.register(Project, GuardedModelAdmin)
@ -95,16 +95,22 @@ class PluginAdmin(admin.ModelAdmin):
def description(self, obj):
manifest = get_plugin_by_name(obj.name, only_active=False, refresh_cache_if_none=True).get_manifest()
return manifest.get('description', '')
return _(manifest.get('description', ''))
description.short_description = _("Description")
def version(self, obj):
manifest = get_plugin_by_name(obj.name, only_active=False, refresh_cache_if_none=True).get_manifest()
return manifest.get('version', '')
version.short_description = _("Version")
def author(self, obj):
manifest = get_plugin_by_name(obj.name, only_active=False, refresh_cache_if_none=True).get_manifest()
return manifest.get('author', '')
author.short_description = _("Author")
def get_urls(self):
urls = super().get_urls()
custom_urls = [
@ -224,7 +230,7 @@ class PluginAdmin(admin.ModelAdmin):
obj.name
)
plugin_actions.short_description = 'Actions'
plugin_actions.short_description = _('Actions')
plugin_actions.allow_tags = True

Wyświetl plik

@ -3,7 +3,7 @@ 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"), verbose_name=_("Name"))
enabled = models.BooleanField(db_index=True, default=True, help_text=_("Whether this plugin is enabled."), verbose_name="Enabled")
enabled = models.BooleanField(db_index=True, default=True, help_text=_("Whether this plugin is enabled."), verbose_name=_("Enabled"))
def __str__(self):
return self.name

Wyświetl plik

@ -0,0 +1,33 @@
#!/usr/bin/python3
import argparse, json, os, glob
parser = argparse.ArgumentParser(description='Extract plugin manifest strings.')
parser.add_argument('input', type=str,
help='Path to plugins directory')
parser.add_argument('output', type=str,
help='Where to write resulting translation file')
args = parser.parse_args()
strings = []
manifests = glob.glob(os.path.join(args.input, "*", "manifest.json"), recursive=True)
print("Found %s manifests" % len(manifests))
for m in manifests:
with open(m) as f:
j = json.loads(f.read())
if j.get("description"):
strings.append(j.get("description"))
print("Found %s manifest strings" % len(strings))
if len(strings) > 0:
with open(args.output, "w") as f:
f.write("// Auto-generated with extract_plugin_manifest_strings.py, do not edit!\n\n")
f.write("from django.utils.translation import gettext as _\n")
for s in strings:
f.write("_(\"%s\")\n" % s.replace("\"", "\\\""))
print("Wrote %s" % args.output)
else:
print("No strings found")

Wyświetl plik

@ -0,0 +1,81 @@
// Auto-generated with extract_odm_strings.py, do not edit!
_("Oct-tree depth used in the mesh reconstruction, increase to get more vertices, recommended values are 8-12. Default: %(default)s");
_("Build orthophoto overviews using gdaladdo.");
_("Set a value in meters for the GPS Dilution of Precision (DOP) information for all images. If your images are tagged with high precision GPS information (RTK), this value will be automatically set accordingly. You can use this option to manually set it in case the reconstruction fails. Lowering this option can sometimes help control bowling-effects over large areas. Default: %(default)s");
_("Number of steps used to fill areas with gaps. Set to 0 to disable gap filling. Starting with a radius equal to the output resolution, N different DEMs are generated with progressively bigger radius using the inverse distance weighted (IDW) algorithm and merged together. Remaining gaps are then merged using nearest neighbor interpolation. Default=%(default)s");
_("Set a camera projection type. Manually setting a value can help improve geometric undistortion. By default the application tries to determine a lens type from the images metadata. Can be set to one of: %(choices)s. Default: %(default)s");
_("Legacy option (use --pc-quality instead). Controls the density of the point cloud by setting the resolution of the depthmap images. Higher values take longer to compute but produce denser point clouds. Default: %(default)s");
_("Generates a polygon around the cropping area that cuts the orthophoto around the edges of features. This polygon can be useful for stitching seamless mosaics with multiple overlapping orthophotos. Default: %(default)s");
_("Set feature extraction quality. Higher quality generates better features, but requires more memory and takes longer. Can be one of: %(choices)s. Default: %(default)s");
_("Minimum number of features to extract per image. More features leads to better results but slower execution. Default: %(default)s");
_("Export the georeferenced point cloud in LAS format. Default: %(default)s");
_("DSM/DTM resolution in cm / pixel. Note that this value is capped by a ground sampling distance (GSD) estimate. To remove the cap, check --ignore-gsd also.Default: %(default)s");
_("Simple Morphological Filter slope parameter (rise over run). Default: %(default)s");
_("Use this tag to build a DTM (Digital Terrain Model, ground only) using a simple morphological filter. Check the --dem* and --smrf* parameters for finer tuning.");
_("Orthophoto resolution in cm / pixel. Note that this value is capped by a ground sampling distance (GSD) estimate. To remove the cap, check --ignore-gsd also.Default: %(default)s");
_("Split multi-track reconstructions.");
_("Use this tag to build a DSM (Digital Surface Model, ground + objects) using a progressive morphological filter. Check the --dem* parameters for finer tuning.");
_("The maximum number of processes to use in various processes. Peak memory requirement is ~1GB per thread and 2 megapixel image resolution. Default: %(default)s");
_("When using PATCH_MATCH or PATCH_MATCH_SAMPLE, controls the standard deviation threshold to include patches. Patches with lower standard deviation are ignored. Default: %(default)s");
_("Use a full 3D mesh to compute the orthophoto instead of a 2.5D mesh. This option is a bit faster and provides similar results in planar areas.");
_("Radius of the overlap between submodels. After grouping images into clusters, images that are closer than this radius to a cluster are added to the cluster. This is done to ensure that neighboring submodels overlap.");
_("Export the georeferenced point cloud in Entwine Point Tile (EPT) format. Default: %(default)s");
_("Skip generation of a full 3D model. This can save time if you only need 2D results such as orthophotos and DEMs.");
_("Skips dense reconstruction and 3D model generation. It generates an orthophoto directly from the sparse reconstruction. If you just need an orthophoto and do not need a full 3D model, turn on this option.");
_("Use the camera parameters computed from another dataset instead of calculating them. Can be specified either as path to a cameras.json file or as a JSON string representing the contents of a cameras.json file. Default: %(default)s");
_("URL to a ClusterODM instance for distributing a split-merge workflow on multiple nodes in parallel. Default: %(default)s");
_("When processing multispectral datasets, you can specify the name of the primary band that will be used for reconstruction. It's recommended to choose a band which has sharp details and is in focus. Default: %(default)s");
_("Number of points per octree node, recommended and default value: %(default)s");
_("Print additional messages to the consoleDefault: %(default)s");
_("Data term: [area, gmi]. Default: %(default)s");
_("Path to the file containing the ground control points used for georeferencing. Default: %(default)s. The file needs to use the following format: EPSG:<code> or <+proj definition>geo_x geo_y geo_z im_x im_y image_name [gcp_name] [extra1] [extra2]");
_("Path to the project folder");
_("Skip local seam blending. Default: %(default)s");
_("Skip global seam leveling. Useful for IR data.Default: %(default)s");
_("Number of nearest images to pre-match based on GPS exif data. Set to 0 to skip pre-matching. Neighbors works together with Distance parameter, set both to 0 to not use pre-matching. OpenSFM uses both parameters at the same time, Bundler uses only one which has value, prefering the Neighbors parameter. Default: %(default)s");
_("Can be one of:dataset | split | merge | opensfm | openmvs | odm_filterpoints | odm_meshing | mvs_texturing | odm_georeferencing | odm_dem | odm_orthophoto | odm_report");
_("Set the radiometric calibration to perform on images. When processing multispectral images you should set this option to obtain reflectance values (otherwise you will get digital number values). [camera] applies black level, vignetting, row gradient gain/exposure compensation (if appropriate EXIF tags are found). [camera+sun] is experimental, applies all the corrections of [camera], plus compensates for spectral radiance registered via a downwelling light sensor (DLS) taking in consideration the angle of the sun. Can be set to one of: %(choices)s. Default: %(default)s");
_("Generate static tiles for orthophotos and DEMs that are suitable for viewers like Leaflet or OpenLayers. Default: %(default)s");
_("Matcher algorithm, Fast Library for Approximate Nearest Neighbors or Bag of Words. FLANN is slower, but more stable. BOW is faster, but can sometimes miss valid matches. Can be one of: %(choices)s. Default: %(default)s");
_("Generates a benchmark file with runtime infoDefault: %(default)s");
_("Print debug messagesDefault: %(default)s");
_("Raw depthmap computation algorithm. PATCH_MATCH and PATCH_MATCH_SAMPLE are faster, but might miss some valid points. BRUTE_FORCE takes longer but produces denser reconstructions. Default: %(default)s");
_("Classify the point cloud outputs using a Simple Morphological Filter. You can control the behavior of this option by tweaking the --dem-* parameters. Default: %(default)s");
_("Average number of images per submodel. When splitting a large dataset into smaller submodels, images are grouped into clusters. This value regulates the number of images that each cluster should have on average.");
_("Delete heavy intermediate files to optimize disk space usage. This affects the ability to restart the pipeline from an intermediate stage, but allows datasets to be processed on machines that don't have sufficient disk space available. Default: %(default)s");
_("Simple Morphological Filter elevation scalar parameter. Default: %(default)s");
_("Perform ground rectification on the point cloud. This means that wrongly classified ground points will be re-classified and gaps will be filled. Useful for generating DTMs. Default: %(default)s");
_("The maximum vertex count of the output mesh. Default: %(default)s");
_("Filters the point cloud by removing points that deviate more than N standard deviations from the local mean. Set to 0 to disable filtering.Default: %(default)s");
_("Run local bundle adjustment for every image added to the reconstruction and a global adjustment every 100 images. Speeds up reconstruction for very large datasets.");
_("Set the compression to use for orthophotos. Options: %(choices)s.Default: %(default)s");
_("Set this parameter if you want a stripped geoTIFF.Default: %(default)s");
_("Displays version number and exits. ");
_("show this help message and exit");
_("Name of Project (i.e subdirectory of projects folder)");
_("Use opensfm to compute dense point cloud alternatively");
_("force rerun of all tasks");
_("Set point cloud quality. Higher quality generates better, denser point clouds, but requires more memory and takes longer. Each step up in quality increases processing time roughly by a factor of 4x.Can be one of: %(choices)s. Default: %(default)s");
_("Path to the image geolocation file containing the camera center coordinates used for georeferencing. Note that omega/phi/kappa are currently not supported (you can set them to 0). Default: %(default)s. The file needs to use the following format: EPSG:<code> or <+proj definition>image_name geo_x geo_y geo_z [omega (degrees)] [phi (degrees)] [kappa (degrees)] [horz accuracy (meters)] [vert accuracy (meters)]");
_("Turn off camera parameter optimization during bundler");
_("Set this parameter if you want to generate a PNG rendering of the orthophoto.Default: %(default)s");
_("Type of photometric outlier removal method: [none, gauss_damping, gauss_clamping]. Default: %(default)s");
_("Decimate the points before generating the DEM. 1 is no decimation (full quality). 100 decimates ~99%% of the points. Useful for speeding up generation.Default=%(default)s");
_("Use images' GPS exif data for reconstruction, even if there are GCPs present.This flag is useful if you have high precision GPS measurements. If there are no GCPs, this flag does nothing. Default: %(default)s");
_("Computes an euclidean raster map for each DEM. The map reports the distance from each cell to the nearest NODATA value (before any hole filling takes place). This can be useful to isolate the areas that have been filled. Default: %(default)s");
_("Choose the algorithm for extracting keypoints and computing descriptors. Can be one of: %(choices)s. Default: %(default)s");
_("Choose what to merge in the merge step in a split dataset. By default all available outputs are merged. Options: %(choices)s. Default: %(default)s");
_("This floating point value specifies the importance that interpolation of the point samples is given in the formulation of the screened Poisson equation. The results of the original (unscreened) Poisson Reconstruction can be obtained by setting this value to 0.Default= %(default)s");
_("Turn on gamma tone mapping or none for no tone mapping. Choices are 'gamma' or 'none'. Default: %(default)s ");
_("Use this tag if you have a gcp_list.txt but want to use the exif geotags instead");
_("Ignore Ground Sampling Distance (GSD). GSD caps the maximum resolution of image outputs and resizes images when necessary, resulting in faster processing and lower memory usage. Since GSD is an estimate, sometimes ignoring it can result in slightly better image output quality.");
_("Minimum number of views that should reconstruct a point for it to be valid. Use lower values if your images have less overlap. Lower values result in denser point clouds but with more noise. Default: %(default)s");
_("Automatically crop image outputs by creating a smooth buffer around the dataset boundaries, shrinked by N meters. Use 0 to disable cropping. Default: %(default)s");
_("Distance threshold in meters to find pre-matching images based on GPS exif data. Set both matcher-neighbors and this to 0 to skip pre-matching. Default: %(default)s");
_("When processing multispectral datasets, ODM will automatically align the images for each band. If the images have been postprocessed and are already aligned, use this option. Default: %(default)s");
_("Simple Morphological Filter elevation threshold parameter (meters). Default: %(default)s");
_("Export the georeferenced point cloud in CSV format. Default: %(default)s");
_("Simple Morphological Filter window radius parameter (meters). Default: %(default)s");
_("Legacy option (use --feature-quality instead). Resizes images by the largest side for feature extraction purposes only. Set to -1 to disable. This does not affect the final orthophoto resolution quality and will not resize the original images. Default: %(default)s");
_("Filters the point cloud by keeping only a single point around a radius N (in meters). This can be useful to limit the output resolution of the point cloud. Set to 0 to disable sampling.Default: %(default)s");

Wyświetl plik

@ -0,0 +1,15 @@
// Auto-generated with extract_plugin_manifest_strings.py, do not edit!
_("Compute volume, area and length measurements on Leaflet");
_("A plugin to create GCP files from images");
_("A plugin to create GCP files from images");
_("A plugin to add a button for quickly opening OpenStreetMap's iD editor and setup a TMS basemap.");
_("A plugin to upload orthophotos to OpenAerialMap");
_("Display program version, memory and disk space usage statistics");
_("Sync accounts from webodm.net");
_("Add a fullscreen button to the 2D map view");
_("Compute, preview and export contours from DEMs");
_("Calculate and draw an elevation map based on a task's DEMs");
_("Upload and tile ODM assets with Cesium ion.");
_("Import images from external sources directly");
_("Detect changes between two different tasks in the same project.");

Wyświetl plik

@ -0,0 +1,16 @@
// Auto-generated with extract_plugin_manifest_strings.py, do not edit!
from django.utils.translation import gettext as _
_("Compute volume, area and length measurements on Leaflet")
_("A plugin to create GCP files from images")
_("A plugin to create GCP files from images")
_("A plugin to add a button for quickly opening OpenStreetMap's iD editor and setup a TMS basemap.")
_("A plugin to upload orthophotos to OpenAerialMap")
_("Display program version, memory and disk space usage statistics")
_("Sync accounts from webodm.net")
_("Add a fullscreen button to the 2D map view")
_("Compute, preview and export contours from DEMs")
_("Calculate and draw an elevation map based on a task's DEMs")
_("Upload and tile ODM assets with Cesium ion.")
_("Import images from external sources directly")
_("Detect changes between two different tasks in the same project.")

Wyświetl plik

@ -5,6 +5,7 @@ from rest_framework.response import Response
from app.plugins.views import TaskView, CheckTask, GetTaskResult
from worker.tasks import execute_grass_script
from app.plugins.grass_engine import grass, GrassEngineException, cleanup_grass_context
from django.utils.translation import gettext_lazy as _
class TaskContoursGenerate(TaskView):
def post(self, request, pk=None):
@ -12,9 +13,9 @@ class TaskContoursGenerate(TaskView):
layer = request.data.get('layer', None)
if layer == 'DSM' and task.dsm_extent is None:
return Response({'error': 'No DSM layer is available.'})
return Response({'error': _('No DSM layer is available.')})
elif layer == 'DTM' and task.dtm_extent is None:
return Response({'error': 'No DTM layer is available.'})
return Response({'error': _('No DTM layer is available.')})
try:
if layer == 'DSM':
@ -56,7 +57,7 @@ class TaskContoursCheck(CheckTask):
def error_check(self, result):
contours_file = result.get('file')
if not contours_file or not os.path.exists(contours_file):
return 'Contours file could not be generated. This might be a bug.'
return _('Contours file could not be generated. This might be a bug.')
class TaskContoursDownload(GetTaskResult):
pass

Wyświetl plik

@ -5,6 +5,7 @@ import L from 'leaflet';
import './ContoursPanel.scss';
import ErrorMessage from 'webodm/components/ErrorMessage';
import Workers from 'webodm/classes/Workers';
import { _ } from 'webodm/classes/gettext';
export default class ContoursPanel extends React.Component {
static defaultProps = {
@ -53,11 +54,11 @@ export default class ContoursPanel extends React.Component {
if (layers.length > 0){
this.setState({layers, layer: layers[0]});
}else{
this.setState({permanentError: "No DSM or DTM is available. To export contours, make sure to process a task with either the --dsm or --dtm option checked."});
this.setState({permanentError: _("No DSM or DTM is available. To export contours, make sure to process a task with either the --dsm or --dtm option checked.")});
}
})
.fail(() => {
this.setState({permanentError: `Cannot retrieve information for task ${id}. Are you are connected to the internet?`})
this.setState({permanentError: _("Cannot retrieve information for task. Are you are connected to the internet?")})
})
.always(() => {
this.setState({loading: false});
@ -127,7 +128,7 @@ export default class ContoursPanel extends React.Component {
this.setState({previewLayer: L.geoJSON(geojson, {
onEachFeature: (feature, layer) => {
if (feature.properties && feature.properties.level !== undefined) {
layer.bindPopup(`<b>Elevation:</b> ${feature.properties.level} meters`);
layer.bindPopup(`<b>${_("Elevation:")}</b> ${feature.properties.level} ${_("meters")}`);
}
},
style: feature => {
@ -223,40 +224,40 @@ export default class ContoursPanel extends React.Component {
simplify, customSimplify,
previewLoading, previewLayer } = this.state;
const intervalValues = [0.25, 0.5, 1, 1.5, 2];
const simplifyValues = [{label: 'Do not simplify', value: 0},
{label: 'Normal', value: 0.2},
{label: 'Aggressive', value: 1}];
const simplifyValues = [{label: _('Do not simplify'), value: 0},
{label: _('Normal'), value: 0.2},
{label: _('Aggressive'), value: 1}];
const disabled = (interval === "custom" && !customInterval) ||
(epsg === "custom" && !customEpsg) ||
(simplify === "custom" && !customSimplify);
let content = "";
if (loading) content = (<span><i className="fa fa-circle-notch fa-spin"></i> Loading...</span>);
if (loading) content = (<span><i className="fa fa-circle-notch fa-spin"></i> {_("Loading...")}</span>);
else if (permanentError) content = (<div className="alert alert-warning">{permanentError}</div>);
else{
content = (<div>
<ErrorMessage bind={[this, "error"]} />
<div className="row form-group form-inline">
<label className="col-sm-3 control-label">Interval:</label>
<label className="col-sm-3 control-label">{_("Interval:")}</label>
<div className="col-sm-9 ">
<select className="form-control" value={interval} onChange={this.handleSelectInterval}>
{intervalValues.map(iv => <option value={iv}>{iv} meter</option>)}
<option value="custom">Custom</option>
{intervalValues.map(iv => <option value={iv}>{iv} {_("meter")}</option>)}
<option value="custom">{_("Custom")}</option>
</select>
</div>
</div>
{interval === "custom" ?
<div className="row form-group form-inline">
<label className="col-sm-3 control-label">Value:</label>
<label className="col-sm-3 control-label">{_("Value:")}</label>
<div className="col-sm-9 ">
<input type="number" className="form-control custom-interval" value={customInterval} onChange={this.handleChangeCustomInterval} /><span> meter</span>
<input type="number" className="form-control custom-interval" value={customInterval} onChange={this.handleChangeCustomInterval} /><span> {_("meter")}</span>
</div>
</div>
: ""}
<div className="row form-group form-inline">
<label className="col-sm-3 control-label">Layer:</label>
<label className="col-sm-3 control-label">{_("Layer:")}</label>
<div className="col-sm-9 ">
<select className="form-control" value={layer} onChange={this.handleSelectLayer}>
{layers.map(l => <option value={l}>{l}</option>)}
@ -265,30 +266,30 @@ export default class ContoursPanel extends React.Component {
</div>
<div className="row form-group form-inline">
<label className="col-sm-3 control-label">Simplify:</label>
<label className="col-sm-3 control-label">{_("Simplify:")}</label>
<div className="col-sm-9 ">
<select className="form-control" value={simplify} onChange={this.handleSelectSimplify}>
{simplifyValues.map(sv => <option value={sv.value}>{sv.label} ({sv.value} meter)</option>)}
<option value="custom">Custom</option>
{simplifyValues.map(sv => <option value={sv.value}>{sv.label} ({sv.value} {_("meter")})</option>)}
<option value="custom">{_("Custom")}</option>
</select>
</div>
</div>
{simplify === "custom" ?
<div className="row form-group form-inline">
<label className="col-sm-3 control-label">Value:</label>
<label className="col-sm-3 control-label">{_("Value:")}</label>
<div className="col-sm-9 ">
<input type="number" className="form-control custom-interval" value={customSimplify} onChange={this.handleChangeCustomSimplify} /><span> meter</span>
<input type="number" className="form-control custom-interval" value={customSimplify} onChange={this.handleChangeCustomSimplify} /><span> {_("meter")}</span>
</div>
</div>
: ""}
<div className="row form-group form-inline">
<label className="col-sm-3 control-label">Projection:</label>
<label className="col-sm-3 control-label">{_("Projection:")}</label>
<div className="col-sm-9 ">
<select className="form-control" value={epsg} onChange={this.handleSelectEpsg}>
<option value="4326">WGS84 (EPSG:4326)</option>
<option value="3857">Web Mercator (EPSG:3857)</option>
<option value="custom">Custom EPSG</option>
<option value="custom">{_("Custom")} EPSG</option>
</select>
</div>
</div>
@ -310,12 +311,12 @@ export default class ContoursPanel extends React.Component {
<div className="col-sm-9 text-right">
<button onClick={this.handleShowPreview}
disabled={disabled || previewLoading} type="button" className="btn btn-sm btn-primary btn-preview">
{previewLoading ? <i className="fa fa-spin fa-circle-notch"/> : <i className="glyphicon glyphicon-eye-open"/>} Preview
{previewLoading ? <i className="fa fa-spin fa-circle-notch"/> : <i className="glyphicon glyphicon-eye-open"/>} {_("Preview")}
</button>
<div className="btn-group">
<button disabled={disabled || exportLoading} type="button" className="btn btn-sm btn-primary" data-toggle="dropdown">
{exportLoading ? <i className="fa fa-spin fa-circle-notch"/> : <i className="glyphicon glyphicon-download" />} Export
{exportLoading ? <i className="fa fa-spin fa-circle-notch"/> : <i className="glyphicon glyphicon-download" />} {_("Export")}
</button>
<button disabled={disabled|| exportLoading} type="button" className="btn btn-sm dropdown-toggle btn-primary" data-toggle="dropdown"><span className="caret"></span></button>
<ul className="dropdown-menu pull-right">
@ -348,7 +349,7 @@ export default class ContoursPanel extends React.Component {
return (<div className="contours-panel">
<span className="close-button" onClick={this.props.onClose}/>
<div className="title">Contours</div>
<div className="title">{_("Contours")}</div>
<hr/>
{content}
</div>);

Wyświetl plik

@ -14,6 +14,7 @@ if [[ "$1" == "extract" ]]; then
python3 app/scripts/extract_potree_strings.py app/static/app/js/vendor/potree/build/potree/resources/lang/en/translation.json app/static/app/js/translations/potree_autogenerated.js
python3 app/scripts/extract_odm_strings.py https://raw.githubusercontent.com/OpenDroneMap/ODM/master/opendm/config.py app/static/app/js/translations/odm_autogenerated.js
python3 app/scripts/extract_plugin_manifest_strings.py plugins/ app/translations/plugin_manifest_autogenerated.py
django-admin makemessages --keep-pot $locale_param --ignore=build --ignore=app/templates/app/registration/*
python manage.py makemessages_djangojs --keep-pot $locale_param -d djangojs --extension jsx --extension js --ignore=build --ignore app/static/app/js/vendor --ignore app/static/app/bundles --ignore node_modules --language Python