2
0
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:
Oliver 2024-10-09 22:32:34 +11:00 committed by GitHub
parent 0b87dc9372
commit dde6aab8b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 170 additions and 81 deletions

100
.github/scripts/check_source_strings.py vendored Normal file
View 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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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],

View File

@ -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

View File

@ -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)'))

View File

@ -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.

View File

@ -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),
), ),
] ]

View File

@ -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={

View File

@ -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(

View File

@ -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"

View File

@ -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')),

View File

@ -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>

View File

@ -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."""

View File

@ -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')),
], ],

View File

@ -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),
), ),
] ]

View File

@ -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')),

View File

@ -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) {

View File

@ -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={}) {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>
}, },

View File

@ -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'
}); });

View File

@ -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>

View File

@ -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>

View File

@ -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>
)} )}

View File

@ -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>

View File

@ -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

View File

@ -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({

View File

@ -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' }} />
), ),

View File

@ -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>
)} )}

View File

@ -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)}
/> />

View File

@ -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',

View File

@ -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();

View File

@ -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)}
/> />

View File

@ -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()}
/> />

View File

@ -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>
), ),

View File

@ -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

View File

@ -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();

View File

@ -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

View File

@ -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`}
/> />
]; ];
}, []); }, []);

View File

@ -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();