mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 03:26:45 +00:00
Translation fixes (#8263)
* Translation fixes - Simplifies translations strings - Removes some similar duplicate strings - Reduces passing of tokens into translation * Adds script for detecting close matches in translation source strings * Updates for custom script * Detect duplicate strings (ignoring case) * Fix some duplicate backend strings * Fix duplicate strings in frontend * Fix more duplicate strings * Run check_source_strings in CI * Fixes for unit tests * Fix another broken string * Revert some changes * Fix f-string * Fix old migration files * Reduce front-end duplication * Further updates * Revert change * Updates
This commit is contained in:
parent
0b87dc9372
commit
dde6aab8b4
100
.github/scripts/check_source_strings.py
vendored
Normal file
100
.github/scripts/check_source_strings.py
vendored
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
"""Script to check source strings for translations."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
|
||||||
|
import rapidfuzz
|
||||||
|
|
||||||
|
BACKEND_SOURCE_FILE = [
|
||||||
|
'..',
|
||||||
|
'..',
|
||||||
|
'src',
|
||||||
|
'backend',
|
||||||
|
'InvenTree',
|
||||||
|
'locale',
|
||||||
|
'en',
|
||||||
|
'LC_MESSAGES',
|
||||||
|
'django.po',
|
||||||
|
]
|
||||||
|
|
||||||
|
FRONTEND_SOURCE_FILE = [
|
||||||
|
'..',
|
||||||
|
'..',
|
||||||
|
'src',
|
||||||
|
'frontend',
|
||||||
|
'src',
|
||||||
|
'locales',
|
||||||
|
'en',
|
||||||
|
'messages.po',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def extract_source_strings(file_path):
|
||||||
|
"""Extract source strings from the provided file."""
|
||||||
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
abs_file_path = os.path.abspath(os.path.join(here, *file_path))
|
||||||
|
|
||||||
|
sources = []
|
||||||
|
|
||||||
|
with open(abs_file_path, encoding='utf-8') as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith('msgid '):
|
||||||
|
msgid = line[6:].strip()
|
||||||
|
|
||||||
|
if msgid in sources:
|
||||||
|
print(f'Duplicate source string: {msgid}')
|
||||||
|
else:
|
||||||
|
sources.append(msgid)
|
||||||
|
|
||||||
|
return sources
|
||||||
|
|
||||||
|
|
||||||
|
def compare_source_strings(sources, threshold):
|
||||||
|
"""Compare source strings to find duplicates (or close matches)."""
|
||||||
|
issues = 0
|
||||||
|
|
||||||
|
for i, source in enumerate(sources):
|
||||||
|
for other in sources[i + 1 :]:
|
||||||
|
if other.lower() == source.lower():
|
||||||
|
print(f'- Duplicate: {source} ~ {other}')
|
||||||
|
issues += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
ratio = rapidfuzz.fuzz.ratio(source, other)
|
||||||
|
if ratio > threshold:
|
||||||
|
print(f'- Close match: {source} ~ {other} ({ratio:.1f}%)')
|
||||||
|
issues += 1
|
||||||
|
|
||||||
|
if issues:
|
||||||
|
print(f' - Found {issues} issues.')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Check source strings for translations.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--backend', action='store_true', help='Check backend source strings'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--frontend', action='store_true', help='Check frontend source strings'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--threshold',
|
||||||
|
type=int,
|
||||||
|
help='Set the threshold for string comparison',
|
||||||
|
default=99,
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.backend:
|
||||||
|
backend_sources = extract_source_strings(BACKEND_SOURCE_FILE)
|
||||||
|
print('Backend source strings:', len(backend_sources))
|
||||||
|
compare_source_strings(backend_sources, args.threshold)
|
||||||
|
|
||||||
|
if args.frontend:
|
||||||
|
frontend_sources = extract_source_strings(FRONTEND_SOURCE_FILE)
|
||||||
|
print('Frontend source strings:', len(frontend_sources))
|
||||||
|
compare_source_strings(frontend_sources, args.threshold)
|
3
.github/workflows/check_translations.yaml
vendored
3
.github/workflows/check_translations.yaml
vendored
@ -38,5 +38,8 @@ jobs:
|
|||||||
apt-dependency: gettext
|
apt-dependency: gettext
|
||||||
- name: Test Translations
|
- name: Test Translations
|
||||||
run: invoke dev.translate
|
run: invoke dev.translate
|
||||||
|
- name: Check for Duplicates
|
||||||
|
run: |
|
||||||
|
python ./.github/scripts/check_source_strings.py --frontend --backend
|
||||||
- name: Check Migration Files
|
- name: Check Migration Files
|
||||||
run: python3 .github/scripts/check_migration_files.py
|
run: python3 .github/scripts/check_migration_files.py
|
||||||
|
@ -204,7 +204,7 @@ def convert_physical_value(value: str, unit: Optional[str] = None, strip_units=T
|
|||||||
if unit:
|
if unit:
|
||||||
raise ValidationError(_(f'Could not convert {original} to {unit}'))
|
raise ValidationError(_(f'Could not convert {original} to {unit}'))
|
||||||
else:
|
else:
|
||||||
raise ValidationError(_('Invalid quantity supplied'))
|
raise ValidationError(_('Invalid quantity provided'))
|
||||||
|
|
||||||
# Calculate the "magnitude" of the value, as a float
|
# Calculate the "magnitude" of the value, as a float
|
||||||
# If the value is specified strangely (e.g. as a fraction or a dozen), this can cause issues
|
# If the value is specified strangely (e.g. as a fraction or a dozen), this can cause issues
|
||||||
@ -218,7 +218,7 @@ def convert_physical_value(value: str, unit: Optional[str] = None, strip_units=T
|
|||||||
|
|
||||||
magnitude = float(ureg.Quantity(magnitude).to_base_units().magnitude)
|
magnitude = float(ureg.Quantity(magnitude).to_base_units().magnitude)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise ValidationError(_(f'Invalid quantity supplied ({exc})'))
|
raise ValidationError(_('Invalid quantity provided') + f': ({exc})')
|
||||||
|
|
||||||
if strip_units:
|
if strip_units:
|
||||||
return magnitude
|
return magnitude
|
||||||
|
@ -551,7 +551,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
|
|||||||
|
|
||||||
if a == b:
|
if a == b:
|
||||||
# Invalid group
|
# Invalid group
|
||||||
add_error(_(f'Invalid group range: {group}'))
|
add_error(_(f'Invalid group: {group}'))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
group_items = []
|
group_items = []
|
||||||
@ -594,7 +594,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
|
|||||||
for item in group_items:
|
for item in group_items:
|
||||||
add_serial(item)
|
add_serial(item)
|
||||||
else:
|
else:
|
||||||
add_error(_(f'Invalid group range: {group}'))
|
add_error(_(f'Invalid group: {group}'))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# In the case of a different number of hyphens, simply add the entire group
|
# In the case of a different number of hyphens, simply add the entire group
|
||||||
@ -612,14 +612,14 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
|
|||||||
sequence_count = max(0, expected_quantity - len(serials))
|
sequence_count = max(0, expected_quantity - len(serials))
|
||||||
|
|
||||||
if len(items) > 2 or len(items) == 0:
|
if len(items) > 2 or len(items) == 0:
|
||||||
add_error(_(f'Invalid group sequence: {group}'))
|
add_error(_(f'Invalid group: {group}'))
|
||||||
continue
|
continue
|
||||||
elif len(items) == 2:
|
elif len(items) == 2:
|
||||||
try:
|
try:
|
||||||
if items[1]:
|
if items[1]:
|
||||||
sequence_count = int(items[1]) + 1
|
sequence_count = int(items[1]) + 1
|
||||||
except ValueError:
|
except ValueError:
|
||||||
add_error(_(f'Invalid group sequence: {group}'))
|
add_error(_(f'Invalid group: {group}'))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
value = items[0]
|
value = items[0]
|
||||||
@ -638,7 +638,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value=
|
|||||||
for item in sequence_items:
|
for item in sequence_items:
|
||||||
add_serial(item)
|
add_serial(item)
|
||||||
else:
|
else:
|
||||||
add_error(_(f'Invalid group sequence: {group}'))
|
add_error(_(f'Invalid group: {group}'))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# At this point, we assume that the 'group' is just a single serial value
|
# At this point, we assume that the 'group' is just a single serial value
|
||||||
|
@ -25,7 +25,7 @@ def send_simple_login_email(user, link):
|
|||||||
)
|
)
|
||||||
|
|
||||||
send_mail(
|
send_mail(
|
||||||
_(f'[{site_name}] Log in to the app'),
|
f'[{site_name}] ' + _('Log in to the app'),
|
||||||
email_plaintext_message,
|
email_plaintext_message,
|
||||||
settings.DEFAULT_FROM_EMAIL,
|
settings.DEFAULT_FROM_EMAIL,
|
||||||
[user.email],
|
[user.email],
|
||||||
|
@ -624,7 +624,7 @@ class DataFileUploadSerializer(serializers.Serializer):
|
|||||||
accepted_file_types = ['xls', 'xlsx', 'csv', 'tsv', 'xml']
|
accepted_file_types = ['xls', 'xlsx', 'csv', 'tsv', 'xml']
|
||||||
|
|
||||||
if ext not in accepted_file_types:
|
if ext not in accepted_file_types:
|
||||||
raise serializers.ValidationError(_('Unsupported file type'))
|
raise serializers.ValidationError(_('Unsupported file format'))
|
||||||
|
|
||||||
# Impose a 50MB limit on uploaded BOM files
|
# Impose a 50MB limit on uploaded BOM files
|
||||||
max_upload_file_size = 50 * 1024 * 1024
|
max_upload_file_size = 50 * 1024 * 1024
|
||||||
|
@ -60,7 +60,7 @@ class FileManager:
|
|||||||
file.seek(0)
|
file.seek(0)
|
||||||
else:
|
else:
|
||||||
fmt = ext.upper()
|
fmt = ext.upper()
|
||||||
raise ValidationError(_(f'Unsupported file format: {fmt}'))
|
raise ValidationError(_('Unsupported file format') + f': {fmt}')
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
raise ValidationError(_('Error reading file (invalid encoding)'))
|
raise ValidationError(_('Error reading file (invalid encoding)'))
|
||||||
|
|
||||||
|
@ -22,8 +22,8 @@ class UploadFileForm(forms.Form):
|
|||||||
|
|
||||||
if name:
|
if name:
|
||||||
# Update label and help_text with file name
|
# Update label and help_text with file name
|
||||||
self.fields['file'].label = _(f'{name.title()} File')
|
self.fields['file'].label = name.title() + ' ' + _('File')
|
||||||
self.fields['file'].help_text = _(f'Select {name} file to upload')
|
self.fields['file'].help_text = _('Select file to upload')
|
||||||
|
|
||||||
def clean_file(self):
|
def clean_file(self):
|
||||||
"""Run tabular file validation.
|
"""Run tabular file validation.
|
||||||
|
@ -18,6 +18,6 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='inventreesetting',
|
model_name='inventreesetting',
|
||||||
name='key',
|
name='key',
|
||||||
field=models.CharField(help_text='Settings key (must be unique - case insensitive', max_length=50, unique=True),
|
field=models.CharField(help_text='Settings key', max_length=50, unique=True),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -18,7 +18,7 @@ class Migration(migrations.Migration):
|
|||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('value', models.CharField(blank=True, help_text='Settings value', max_length=200)),
|
('value', models.CharField(blank=True, help_text='Settings value', max_length=200)),
|
||||||
('key', models.CharField(help_text='Settings key (must be unique - case insensitive', max_length=50)),
|
('key', models.CharField(help_text='Settings key', max_length=50)),
|
||||||
('user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
('user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
|
@ -780,10 +780,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
key = models.CharField(
|
key = models.CharField(
|
||||||
max_length=50,
|
max_length=50, blank=False, unique=False, help_text=_('Settings key')
|
||||||
blank=False,
|
|
||||||
unique=False,
|
|
||||||
help_text=_('Settings key (must be unique - case insensitive)'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
value = models.CharField(
|
value = models.CharField(
|
||||||
@ -2179,10 +2176,7 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
|||||||
typ = 'inventree'
|
typ = 'inventree'
|
||||||
|
|
||||||
key = models.CharField(
|
key = models.CharField(
|
||||||
max_length=50,
|
max_length=50, blank=False, unique=True, help_text=_('Settings key')
|
||||||
blank=False,
|
|
||||||
unique=True,
|
|
||||||
help_text=_('Settings key (must be unique - case insensitive'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_native_value(self):
|
def to_native_value(self):
|
||||||
@ -2559,10 +2553,7 @@ class InvenTreeUserSetting(BaseInvenTreeSetting):
|
|||||||
extra_unique_fields = ['user']
|
extra_unique_fields = ['user']
|
||||||
|
|
||||||
key = models.CharField(
|
key = models.CharField(
|
||||||
max_length=50,
|
max_length=50, blank=False, unique=False, help_text=_('Settings key')
|
||||||
blank=False,
|
|
||||||
unique=False,
|
|
||||||
help_text=_('Settings key (must be unique - case insensitive'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
|
@ -40,7 +40,7 @@ msgstr "למשתמש אין הרשאה לצפות במוזל הזה"
|
|||||||
#: InvenTree/conversion.py:161
|
#: InvenTree/conversion.py:161
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Invalid unit provided ({unit})"
|
msgid "Invalid unit provided ({unit})"
|
||||||
msgstr "סופקה יחידה שלא קיימת"
|
msgstr "סופקה יחידה שלא קיימת ({unit})"
|
||||||
|
|
||||||
#: InvenTree/conversion.py:178
|
#: InvenTree/conversion.py:178
|
||||||
msgid "No value provided"
|
msgid "No value provided"
|
||||||
@ -49,7 +49,7 @@ msgstr "לא צוין ערך"
|
|||||||
#: InvenTree/conversion.py:205
|
#: InvenTree/conversion.py:205
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Could not convert {original} to {unit}"
|
msgid "Could not convert {original} to {unit}"
|
||||||
msgstr "לא ניתן להמיר מקור ליחידה"
|
msgstr ""
|
||||||
|
|
||||||
#: InvenTree/conversion.py:207
|
#: InvenTree/conversion.py:207
|
||||||
msgid "Invalid quantity supplied"
|
msgid "Invalid quantity supplied"
|
||||||
|
@ -27,7 +27,7 @@ class Migration(migrations.Migration):
|
|||||||
name='MachineSetting',
|
name='MachineSetting',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('key', models.CharField(help_text='Settings key (must be unique - case insensitive)', max_length=50)),
|
('key', models.CharField(help_text='Settings key', max_length=50)),
|
||||||
('value', models.CharField(blank=True, help_text='Settings value', max_length=2000)),
|
('value', models.CharField(blank=True, help_text='Settings value', max_length=2000)),
|
||||||
('config_type', models.CharField(choices=[('M', 'Machine'), ('D', 'Driver')], max_length=1, verbose_name='Config type')),
|
('config_type', models.CharField(choices=[('M', 'Machine'), ('D', 'Driver')], max_length=1, verbose_name='Config type')),
|
||||||
('machine_config', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='settings', to='machine.machineconfig', verbose_name='Machine Config')),
|
('machine_config', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='settings', to='machine.machineconfig', verbose_name='Machine Config')),
|
||||||
|
@ -143,7 +143,7 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
{% if order.supplier %}
|
{% if order.supplier %}
|
||||||
<a href="{% url 'company-detail' order.supplier.id %}">{{ order.supplier.name }}</a>{% include "clip.html" %}
|
<a href="{% url 'company-detail' order.supplier.id %}">{{ order.supplier.name }}</a>{% include "clip.html" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<em>{% trans "No suppplier information available" %}</em>
|
<em>{% trans "No supplier information available" %}</em>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -82,7 +82,7 @@ class BomUploadTest(InvenTreeAPITestCase):
|
|||||||
"""POST with an unsupported file type."""
|
"""POST with an unsupported file type."""
|
||||||
response = self.post_bom('sample.txt', b'hello world', expected_code=400)
|
response = self.post_bom('sample.txt', b'hello world', expected_code=400)
|
||||||
|
|
||||||
self.assertIn('Unsupported file type', str(response.data['data_file']))
|
self.assertIn('Unsupported file format', str(response.data['data_file']))
|
||||||
|
|
||||||
def test_broken_file(self):
|
def test_broken_file(self):
|
||||||
"""Test upload with broken (corrupted) files."""
|
"""Test upload with broken (corrupted) files."""
|
||||||
|
@ -15,7 +15,7 @@ class Migration(migrations.Migration):
|
|||||||
name='PluginSetting',
|
name='PluginSetting',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('key', models.CharField(help_text='Settings key (must be unique - case insensitive', max_length=50)),
|
('key', models.CharField(help_text='Settings key', max_length=50)),
|
||||||
('value', models.CharField(blank=True, help_text='Settings value', max_length=200)),
|
('value', models.CharField(blank=True, help_text='Settings value', max_length=200)),
|
||||||
('plugin', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='settings', to='plugin.pluginconfig', verbose_name='Plugin')),
|
('plugin', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='settings', to='plugin.pluginconfig', verbose_name='Plugin')),
|
||||||
],
|
],
|
||||||
|
@ -13,6 +13,6 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='pluginsetting',
|
model_name='pluginsetting',
|
||||||
name='key',
|
name='key',
|
||||||
field=models.CharField(help_text='Settings key (must be unique - case insensitive)', max_length=50),
|
field=models.CharField(help_text='Settings key', max_length=50),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -17,7 +17,7 @@ class Migration(migrations.Migration):
|
|||||||
name='NotificationUserSetting',
|
name='NotificationUserSetting',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('key', models.CharField(help_text='Settings key (must be unique - case insensitive)', max_length=50)),
|
('key', models.CharField(help_text='Settings key', max_length=50)),
|
||||||
('value', models.CharField(blank=True, help_text='Settings value', max_length=200)),
|
('value', models.CharField(blank=True, help_text='Settings value', max_length=200)),
|
||||||
('method', models.CharField(max_length=255, verbose_name='Method')),
|
('method', models.CharField(max_length=255, verbose_name='Method')),
|
||||||
('user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
('user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||||
|
@ -2046,7 +2046,7 @@ function loadSalesOrderLineItemTable(table, options={}) {
|
|||||||
|
|
||||||
if (options.allow_edit && (row.shipped < row.quantity)) {
|
if (options.allow_edit && (row.shipped < row.quantity)) {
|
||||||
if (part.trackable) {
|
if (part.trackable) {
|
||||||
buttons += makeIconButton('fa-hashtag icon-green', 'button-add-by-sn', pk, '{% trans "Allocate serial numbers" %}');
|
buttons += makeIconButton('fa-hashtag icon-green', 'button-add-by-sn', pk, '{% trans "Allocate Serial Numbers" %}');
|
||||||
}
|
}
|
||||||
buttons += makeIconButton('fa-sign-in-alt icon-green', 'button-add', pk, '{% trans "Allocate stock" %}');
|
buttons += makeIconButton('fa-sign-in-alt icon-green', 'button-add', pk, '{% trans "Allocate stock" %}');
|
||||||
if (part.purchaseable) {
|
if (part.purchaseable) {
|
||||||
|
@ -617,7 +617,7 @@ function findStockItemBySerialNumber(part_id) {
|
|||||||
handleFormErrors(
|
handleFormErrors(
|
||||||
{
|
{
|
||||||
'serial': [
|
'serial': [
|
||||||
'{% trans "Enter a serial number" %}',
|
'{% trans "Enter serial number" %}',
|
||||||
]
|
]
|
||||||
}, fields, opts
|
}, fields, opts
|
||||||
);
|
);
|
||||||
@ -1445,14 +1445,14 @@ function removeStockRow(e) {
|
|||||||
function passFailBadge(result) {
|
function passFailBadge(result) {
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
return `<span class='badge badge-right rounded-pill bg-success'>{% trans "PASS" %}</span>`;
|
return `<span class='badge badge-right rounded-pill bg-success'>{% trans "Pass" %}</span>`;
|
||||||
} else {
|
} else {
|
||||||
return `<span class='badge badge-right rounded-pill bg-danger'>{% trans "FAIL" %}</span>`;
|
return `<span class='badge badge-right rounded-pill bg-danger'>{% trans "Fail" %}</span>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function noResultBadge() {
|
function noResultBadge() {
|
||||||
return `<span class='badge badge-right rounded-pill bg-info'>{% trans "NO RESULT" %}</span>`;
|
return `<span class='badge badge-right rounded-pill bg-info'>{% trans "No result" %}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDate(row, date, options={}) {
|
function formatDate(row, date, options={}) {
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<p>
|
<p>
|
||||||
{% trans "An error occurred while attempting to login via your social network account." %}
|
{% trans "An error occurred while attempting to login via your social network account." %}
|
||||||
<br>
|
<br>
|
||||||
{% trans "Contact your system administrator for further information." %}
|
{% trans "Contact your system administrator for further information" %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
@ -36,7 +36,7 @@ export default function ImporterImportProgress({
|
|||||||
<StylishText size="lg">{t`Importing Records`}</StylishText>
|
<StylishText size="lg">{t`Importing Records`}</StylishText>
|
||||||
<Loader />
|
<Loader />
|
||||||
<Text size="lg">
|
<Text size="lg">
|
||||||
{t`Imported rows`}: {session.sessionData.row_count}
|
{t`Imported Rows`}: {session.sessionData.row_count}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -24,7 +24,7 @@ function StartedCard({
|
|||||||
</div>
|
</div>
|
||||||
<Anchor href={link} target="_blank">
|
<Anchor href={link} target="_blank">
|
||||||
<Button>
|
<Button>
|
||||||
<Trans>Read more</Trans>
|
<Trans>Read More</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
</Anchor>
|
</Anchor>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
@ -56,7 +56,7 @@ export function MainMenu() {
|
|||||||
component={Link}
|
component={Link}
|
||||||
to="/settings/user"
|
to="/settings/user"
|
||||||
>
|
>
|
||||||
<Trans>Account settings</Trans>
|
<Trans>Account Settings</Trans>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
{user?.is_staff && (
|
{user?.is_staff && (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
|
@ -459,7 +459,7 @@ export function SearchDrawer({
|
|||||||
color="blue"
|
color="blue"
|
||||||
radius="sm"
|
radius="sm"
|
||||||
variant="light"
|
variant="light"
|
||||||
title={t`No results`}
|
title={t`No Results`}
|
||||||
icon={<IconSearch size="1rem" />}
|
icon={<IconSearch size="1rem" />}
|
||||||
>
|
>
|
||||||
<Trans>No results available for search query</Trans>
|
<Trans>No results available for search query</Trans>
|
||||||
|
@ -8,7 +8,7 @@ export default function GetStartedWidget() {
|
|||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
<Title order={5}>
|
<Title order={5}>
|
||||||
<Trans>Getting started</Trans>
|
<Trans>Getting Started</Trans>
|
||||||
</Title>
|
</Title>
|
||||||
<GettingStartedCarousel items={navDocLinks} />
|
<GettingStartedCarousel items={navDocLinks} />
|
||||||
</span>
|
</span>
|
||||||
|
@ -12,7 +12,7 @@ export const menuItems: menuItemsCollection = {
|
|||||||
},
|
},
|
||||||
profile: {
|
profile: {
|
||||||
id: 'profile',
|
id: 'profile',
|
||||||
text: <Trans>Account settings</Trans>,
|
text: <Trans>Account Settings</Trans>,
|
||||||
link: '/settings/user',
|
link: '/settings/user',
|
||||||
doctext: <Trans>User attributes and design settings.</Trans>
|
doctext: <Trans>User attributes and design settings.</Trans>
|
||||||
},
|
},
|
||||||
|
@ -21,7 +21,7 @@ export function notYetImplemented() {
|
|||||||
*/
|
*/
|
||||||
export function permissionDenied() {
|
export function permissionDenied() {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t`Permission denied`,
|
title: t`Permission Denied`,
|
||||||
message: t`You do not have permission to perform this action`,
|
message: t`You do not have permission to perform this action`,
|
||||||
color: 'red'
|
color: 'red'
|
||||||
});
|
});
|
||||||
|
@ -38,7 +38,7 @@ export default function Reset() {
|
|||||||
type="submit"
|
type="submit"
|
||||||
onClick={() => handleReset(navigate, simpleForm.values)}
|
onClick={() => handleReset(navigate, simpleForm.values)}
|
||||||
>
|
>
|
||||||
<Trans>Send mail</Trans>
|
<Trans>Send Email</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -45,12 +45,7 @@ export default function Set_Password() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// make sure we have a token
|
// make sure we have a token
|
||||||
if (!token || !uid) {
|
if (!token || !uid) {
|
||||||
notifications.show({
|
invalidToken();
|
||||||
title: t`No token provided`,
|
|
||||||
message: t`You need to provide a token to set a new password. Check your inbox for a reset link.`,
|
|
||||||
color: 'red'
|
|
||||||
});
|
|
||||||
navigate('/login');
|
|
||||||
}
|
}
|
||||||
}, [token]);
|
}, [token]);
|
||||||
|
|
||||||
@ -109,7 +104,7 @@ export default function Set_Password() {
|
|||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Button type="submit" onClick={handleSet}>
|
<Button type="submit" onClick={handleSet}>
|
||||||
<Trans>Send mail</Trans>
|
<Trans>Send Email</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -32,7 +32,7 @@ export default function TaskManagementPanel() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{taskInfo?.is_running == false && (
|
{taskInfo?.is_running == false && (
|
||||||
<Alert title={t`Background Worker Not Running`} color="red">
|
<Alert title={t`Background worker not running`} color="red">
|
||||||
<Text>{t`The background task manager service is not running. Contact your system administrator.`}</Text>
|
<Text>{t`The background task manager service is not running. Contact your system administrator.`}</Text>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
@ -34,7 +34,7 @@ export default function UserManagementPanel() {
|
|||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<Anchor component={Link} to={'/settings/system'}>
|
<Anchor component={Link} to={'/settings/system'}>
|
||||||
<Trans>System settings</Trans>
|
<Trans>System Settings</Trans>
|
||||||
</Anchor>
|
</Anchor>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -21,7 +21,7 @@ export default function SaleHistoryPanel({
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
accessor: 'order',
|
accessor: 'order',
|
||||||
title: t`Sale Order`,
|
title: t`Sales Order`,
|
||||||
render: (record: any) => record?.order_detail?.reference,
|
render: (record: any) => record?.order_detail?.reference,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
switchable: false
|
switchable: false
|
||||||
|
@ -346,7 +346,7 @@ export default function ReturnOrderDetail() {
|
|||||||
title: t`Cancel Return Order`,
|
title: t`Cancel Return Order`,
|
||||||
onFormSuccess: refreshInstance,
|
onFormSuccess: refreshInstance,
|
||||||
preFormWarning: t`Cancel this order`,
|
preFormWarning: t`Cancel this order`,
|
||||||
successMessage: t`Order canceled`
|
successMessage: t`Order cancelled`
|
||||||
});
|
});
|
||||||
|
|
||||||
const holdOrder = useCreateApiFormModal({
|
const holdOrder = useCreateApiFormModal({
|
||||||
|
@ -668,7 +668,7 @@ export default function StockDetail() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: t`Add`,
|
name: t`Add`,
|
||||||
tooltip: t`Add stock`,
|
tooltip: t`Add Stock`,
|
||||||
hidden: serialized,
|
hidden: serialized,
|
||||||
icon: <InvenTreeIcon icon="add" iconProps={{ color: 'green' }} />,
|
icon: <InvenTreeIcon icon="add" iconProps={{ color: 'green' }} />,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
@ -677,7 +677,7 @@ export default function StockDetail() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: t`Remove`,
|
name: t`Remove`,
|
||||||
tooltip: t`Remove stock`,
|
tooltip: t`Remove Stock`,
|
||||||
hidden: serialized,
|
hidden: serialized,
|
||||||
icon: <InvenTreeIcon icon="remove" iconProps={{ color: 'red' }} />,
|
icon: <InvenTreeIcon icon="remove" iconProps={{ color: 'red' }} />,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
@ -695,7 +695,7 @@ export default function StockDetail() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: t`Transfer`,
|
name: t`Transfer`,
|
||||||
tooltip: t`Transfer stock`,
|
tooltip: t`Transfer Stock`,
|
||||||
icon: (
|
icon: (
|
||||||
<InvenTreeIcon icon="transfer" iconProps={{ color: 'blue' }} />
|
<InvenTreeIcon icon="transfer" iconProps={{ color: 'blue' }} />
|
||||||
),
|
),
|
||||||
|
@ -38,7 +38,7 @@ export function PartColumn({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{part?.locked && (
|
{part?.locked && (
|
||||||
<Tooltip label={t`Part is locked`}>
|
<Tooltip label={t`Part is Locked`}>
|
||||||
<IconLock size={16} />
|
<IconLock size={16} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
@ -549,7 +549,7 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
color="red"
|
color="red"
|
||||||
title={t`Are you sure you want to delete the selected items?`}
|
title={t`Are you sure you want to delete the selected items?`}
|
||||||
>
|
>
|
||||||
{t`This action cannot be undone!`}
|
{t`This action cannot be undone`}
|
||||||
</Alert>
|
</Alert>
|
||||||
),
|
),
|
||||||
initialData: {
|
initialData: {
|
||||||
@ -652,8 +652,8 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
<ButtonMenu
|
<ButtonMenu
|
||||||
key="barcode-actions"
|
key="barcode-actions"
|
||||||
icon={<IconBarcode />}
|
icon={<IconBarcode />}
|
||||||
label={t`Barcode actions`}
|
label={t`Barcode Actions`}
|
||||||
tooltip={t`Barcode actions`}
|
tooltip={t`Barcode Actions`}
|
||||||
actions={tableProps.barcodeActions ?? []}
|
actions={tableProps.barcodeActions ?? []}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -709,7 +709,7 @@ export function InvenTreeTable<T extends Record<string, any>>({
|
|||||||
variant="transparent"
|
variant="transparent"
|
||||||
aria-label="table-select-filters"
|
aria-label="table-select-filters"
|
||||||
>
|
>
|
||||||
<Tooltip label={t`Table filters`}>
|
<Tooltip label={t`Table Filters`}>
|
||||||
<IconFilter
|
<IconFilter
|
||||||
onClick={() => setFiltersVisible(!filtersVisible)}
|
onClick={() => setFiltersVisible(!filtersVisible)}
|
||||||
/>
|
/>
|
||||||
|
@ -56,7 +56,7 @@ export default function BuildLineTable({
|
|||||||
{
|
{
|
||||||
name: 'available',
|
name: 'available',
|
||||||
label: t`Available`,
|
label: t`Available`,
|
||||||
description: t`Show lines with available stock`
|
description: t`Show items with available stock`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'consumable',
|
name: 'consumable',
|
||||||
|
@ -301,7 +301,7 @@ export default function BuildOutputTable({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
RowEditAction({
|
RowEditAction({
|
||||||
tooltip: t`Edit build output`,
|
tooltip: t`Edit Build Output`,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setSelectedOutputs([record]);
|
setSelectedOutputs([record]);
|
||||||
editBuildOutput.open();
|
editBuildOutput.open();
|
||||||
|
@ -138,7 +138,7 @@ export default function PartParameterTemplateTable() {
|
|||||||
const tableActions = useMemo(() => {
|
const tableActions = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
<AddItemButton
|
<AddItemButton
|
||||||
tooltip={t`Add parameter template`}
|
tooltip={t`Add Parameter Template`}
|
||||||
onClick={() => newTemplate.open()}
|
onClick={() => newTemplate.open()}
|
||||||
hidden={!user.hasAddRole(UserRoles.part)}
|
hidden={!user.hasAddRole(UserRoles.part)}
|
||||||
/>
|
/>
|
||||||
|
@ -106,7 +106,7 @@ export function RelatedPartTable({
|
|||||||
return [
|
return [
|
||||||
<AddItemButton
|
<AddItemButton
|
||||||
key="add-related-part"
|
key="add-related-part"
|
||||||
tooltip={t`Add related part`}
|
tooltip={t`Add Related Part`}
|
||||||
hidden={!user.hasAddRole(UserRoles.part)}
|
hidden={!user.hasAddRole(UserRoles.part)}
|
||||||
onClick={() => newRelatedPart.open()}
|
onClick={() => newRelatedPart.open()}
|
||||||
/>
|
/>
|
||||||
|
@ -305,7 +305,7 @@ export default function PluginListTable() {
|
|||||||
>
|
>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Text>{t`The selected plugin will be uninstalled.`}</Text>
|
<Text>{t`The selected plugin will be uninstalled.`}</Text>
|
||||||
<Text>{t`This action cannot be undone.`}</Text>
|
<Text>{t`This action cannot be undone`}</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Alert>
|
</Alert>
|
||||||
),
|
),
|
||||||
|
@ -342,7 +342,7 @@ export function PurchaseOrderLineItemTable({
|
|||||||
/>,
|
/>,
|
||||||
<AddItemButton
|
<AddItemButton
|
||||||
key="add-line-item"
|
key="add-line-item"
|
||||||
tooltip={t`Add line item`}
|
tooltip={t`Add Line Item`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setInitialData({
|
setInitialData({
|
||||||
order: orderId
|
order: orderId
|
||||||
|
@ -165,7 +165,7 @@ export default function ReturnOrderLineItemTable({
|
|||||||
return [
|
return [
|
||||||
<AddItemButton
|
<AddItemButton
|
||||||
key="add-line-item"
|
key="add-line-item"
|
||||||
tooltip={t`Add line item`}
|
tooltip={t`Add Line Item`}
|
||||||
hidden={!user.hasAddRole(UserRoles.return_order)}
|
hidden={!user.hasAddRole(UserRoles.return_order)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
newLine.open();
|
newLine.open();
|
||||||
|
@ -255,7 +255,7 @@ export default function SalesOrderLineItemTable({
|
|||||||
return [
|
return [
|
||||||
<AddItemButton
|
<AddItemButton
|
||||||
key="add-line-item"
|
key="add-line-item"
|
||||||
tooltip={t`Add line item`}
|
tooltip={t`Add Line Item`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setInitialData({
|
setInitialData({
|
||||||
order: orderId
|
order: orderId
|
||||||
@ -277,7 +277,7 @@ export default function SalesOrderLineItemTable({
|
|||||||
allocated ||
|
allocated ||
|
||||||
!editable ||
|
!editable ||
|
||||||
!user.hasChangeRole(UserRoles.sales_order),
|
!user.hasChangeRole(UserRoles.sales_order),
|
||||||
title: t`Allocate stock`,
|
title: t`Allocate Stock`,
|
||||||
icon: <IconSquareArrowRight />,
|
icon: <IconSquareArrowRight />,
|
||||||
color: 'green',
|
color: 'green',
|
||||||
onClick: notYetImplemented
|
onClick: notYetImplemented
|
||||||
|
@ -112,7 +112,7 @@ export default function CustomStateTable() {
|
|||||||
return [
|
return [
|
||||||
<AddItemButton
|
<AddItemButton
|
||||||
onClick={() => newCustomState.open()}
|
onClick={() => newCustomState.open()}
|
||||||
tooltip={t`Add state`}
|
tooltip={t`Add State`}
|
||||||
/>
|
/>
|
||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -449,7 +449,7 @@ export function StockItemTable({
|
|||||||
disabled={table.selectedRecords.length === 0}
|
disabled={table.selectedRecords.length === 0}
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
name: t`Add stock`,
|
name: t`Add Stock`,
|
||||||
icon: <InvenTreeIcon icon="add" iconProps={{ color: 'green' }} />,
|
icon: <InvenTreeIcon icon="add" iconProps={{ color: 'green' }} />,
|
||||||
tooltip: t`Add a new stock item`,
|
tooltip: t`Add a new stock item`,
|
||||||
disabled: !can_add_stock,
|
disabled: !can_add_stock,
|
||||||
@ -458,7 +458,7 @@ export function StockItemTable({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: t`Remove stock`,
|
name: t`Remove Stock`,
|
||||||
icon: <InvenTreeIcon icon="remove" iconProps={{ color: 'red' }} />,
|
icon: <InvenTreeIcon icon="remove" iconProps={{ color: 'red' }} />,
|
||||||
tooltip: t`Remove some quantity from a stock item`,
|
tooltip: t`Remove some quantity from a stock item`,
|
||||||
disabled: !can_add_stock,
|
disabled: !can_add_stock,
|
||||||
@ -478,7 +478,7 @@ export function StockItemTable({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: t`Transfer stock`,
|
name: t`Transfer Stock`,
|
||||||
icon: (
|
icon: (
|
||||||
<InvenTreeIcon icon="transfer" iconProps={{ color: 'blue' }} />
|
<InvenTreeIcon icon="transfer" iconProps={{ color: 'blue' }} />
|
||||||
),
|
),
|
||||||
@ -525,7 +525,7 @@ export function StockItemTable({
|
|||||||
{
|
{
|
||||||
name: t`Delete stock`,
|
name: t`Delete stock`,
|
||||||
icon: <InvenTreeIcon icon="delete" iconProps={{ color: 'red' }} />,
|
icon: <InvenTreeIcon icon="delete" iconProps={{ color: 'red' }} />,
|
||||||
tooltip: t`Delete stock items`,
|
tooltip: t`Delete Stock Items`,
|
||||||
disabled: !can_delete_stock,
|
disabled: !can_delete_stock,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
deleteStock.open();
|
deleteStock.open();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user