diff --git a/.travis.yml b/.travis.yml index 7941be564d..29a1fe8db1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,8 @@ addons: -sqlite3 before_install: + - sudo apt-get update + - sudo apt-get install gettext - make install - make migrate - cd InvenTree && python3 manage.py createsuperuser --username InvenTreeAdmin --email admin@inventree.com --noinput && cd .. @@ -18,6 +20,7 @@ script: - cd InvenTree && python3 manage.py makemigrations && cd .. - python3 ci/check_migration_files.py - make coverage + - make translate - make style after_success: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ebb9d4a4fc..046826c928 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,6 +8,14 @@ No pushing to master! New featues must be submitted in a separate branch (one br Any required migration files **must** be included in the commit, or the pull-request will be rejected. If you change the underlying database schema, make sure you run `make migrate` and commit the migration files before submitting the PR. +## Update Translation Files + +Any PRs which update translatable strings (i.e. text strings that will appear in the web-front UI) must also update the translation (locale) files to include hooks for the translated strings. + +*This does not mean that all translations must be provided, but that the translation files must include locations for the translated strings to be written.* + +To perform this step, simply run `make_translate` from the top level directory before submitting the PR. + ## Testing Any new code should be covered by unit tests - a submitted PR may not be accepted if the code coverage is decreased. diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index c3d646c660..2ad7ae1c8b 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -17,6 +17,8 @@ import logging import tempfile import yaml +from django.utils.translation import gettext_lazy as _ + def eprint(*args, **kwargs): """ Print a warning message to stderr """ @@ -115,6 +117,7 @@ LOGGING = { MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'corsheaders.middleware.CorsMiddleware', @@ -225,7 +228,20 @@ if not type(EXTRA_URL_SCHEMES) in [list]: # Internationalization # https://docs.djangoproject.com/en/1.10/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = CONFIG.get('language', 'en-us') + +# If a new language translation is supported, it must be added here +LANGUAGES = [ + ('en', _('English')), + ('de', _('German')), + ('fr', _('French')), + ('pk', _('Polish')), +] + +LOCALE_PATHS = ( + os.path.join(BASE_DIR, 'locale/'), +) + TIME_ZONE = 'UTC' diff --git a/InvenTree/config_template.yaml b/InvenTree/config_template.yaml index f362048e35..66c6f6d762 100644 --- a/InvenTree/config_template.yaml +++ b/InvenTree/config_template.yaml @@ -18,6 +18,9 @@ database: #HOST: '' #PORT: '' +# Select default system language (default is 'en-us') +language: en-us + # Set debug to False to run in production mode debug: True diff --git a/InvenTree/locale/de/LC_MESSAGES/django.mo b/InvenTree/locale/de/LC_MESSAGES/django.mo new file mode 100644 index 0000000000..71cbdf3e9d Binary files /dev/null and b/InvenTree/locale/de/LC_MESSAGES/django.mo differ diff --git a/InvenTree/locale/de/LC_MESSAGES/django.po b/InvenTree/locale/de/LC_MESSAGES/django.po new file mode 100644 index 0000000000..de659e0f1f --- /dev/null +++ b/InvenTree/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,694 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-09-27 00:12+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: InvenTree/helpers.py:157 order/models.py:158 order/models.py:203 +msgid "Invalid quantity provided" +msgstr "" + +#: InvenTree/helpers.py:160 +msgid "Empty serial number string" +msgstr "" + +#: InvenTree/helpers.py:181 InvenTree/helpers.py:198 +#, python-brace-format +msgid "Duplicate serial: {n}" +msgstr "" + +#: InvenTree/helpers.py:185 InvenTree/helpers.py:188 InvenTree/helpers.py:191 +#: InvenTree/helpers.py:202 +#, python-brace-format +msgid "Invalid group: {g}" +msgstr "" + +#: InvenTree/helpers.py:208 +msgid "No serial numbers found" +msgstr "" + +#: InvenTree/helpers.py:212 +#, python-brace-format +msgid "Number of unique serial number ({s}) must match quantity ({q})" +msgstr "" + +#: InvenTree/settings.py:235 +msgid "English" +msgstr "" + +#: InvenTree/settings.py:236 +msgid "German" +msgstr "" + +#: InvenTree/settings.py:237 +msgid "French" +msgstr "" + +#: InvenTree/settings.py:238 +msgid "Polish" +msgstr "" + +#: InvenTree/status_codes.py:27 InvenTree/status_codes.py:82 +msgid "Pending" +msgstr "" + +#: InvenTree/status_codes.py:28 +msgid "Placed" +msgstr "" + +#: InvenTree/status_codes.py:29 InvenTree/status_codes.py:85 +msgid "Complete" +msgstr "" + +#: InvenTree/status_codes.py:30 InvenTree/status_codes.py:84 +msgid "Cancelled" +msgstr "" + +#: InvenTree/status_codes.py:31 InvenTree/status_codes.py:62 +msgid "Lost" +msgstr "" + +#: InvenTree/status_codes.py:32 +msgid "Returned" +msgstr "" + +#: InvenTree/status_codes.py:58 +msgid "OK" +msgstr "" + +#: InvenTree/status_codes.py:59 +msgid "Attention needed" +msgstr "" + +#: InvenTree/status_codes.py:60 +msgid "Damaged" +msgstr "" + +#: InvenTree/status_codes.py:61 +msgid "Destroyed" +msgstr "" + +#: InvenTree/status_codes.py:83 +msgid "Allocated" +msgstr "" + +#: InvenTree/validators.py:35 +msgid "Invalid character in part name" +msgstr "" + +#: InvenTree/validators.py:44 +#, python-brace-format +msgid "Illegal character in name ({x})" +msgstr "" + +#: InvenTree/validators.py:63 InvenTree/validators.py:79 +msgid "Overage value must not be negative" +msgstr "" + +#: InvenTree/validators.py:81 +msgid "Overage must not exceed 100%" +msgstr "" + +#: InvenTree/validators.py:88 +msgid "Overage must be an integer value or a percentage" +msgstr "" + +#: build/forms.py:36 +msgid "Confirm" +msgstr "" + +#: build/forms.py:53 stock/forms.py:34 +msgid "Enter unique serial numbers (or leave blank)" +msgstr "" + +#: build/forms.py:55 +msgid "Confirm build completion" +msgstr "" + +#: build/models.py:381 +#, python-brace-format +msgid "Selected stock item not found in BOM for part '{p}'" +msgstr "" + +#: build/models.py:384 +#, python-brace-format +msgid "Allocated quantity ({n}) must not exceed available quantity ({q})" +msgstr "" + +#: build/views.py:289 stock/views.py:834 +#, python-brace-format +msgid "The following serial numbers already exist: ({sn})" +msgstr "" + +#: common/models.py:65 +msgid "Settings key (must be unique - case insensitive" +msgstr "" + +#: common/models.py:67 +msgid "Settings value" +msgstr "" + +#: common/models.py:69 +msgid "Settings description" +msgstr "" + +#: common/models.py:82 +msgid "Key string must be unique" +msgstr "" + +#: common/models.py:103 +msgid "Currency Symbol e.g. $" +msgstr "" + +#: common/models.py:105 +msgid "Currency Suffix e.g. AUD" +msgstr "" + +#: common/models.py:107 +msgid "Currency Description" +msgstr "" + +#: common/models.py:109 +msgid "Currency Value" +msgstr "" + +#: common/models.py:111 +msgid "Use this currency as the base currency" +msgstr "" + +#: order/forms.py:21 +msgid "Place order" +msgstr "" + +#: order/forms.py:32 +msgid "Cancel order" +msgstr "" + +#: order/forms.py:43 +msgid "Receive parts to this location" +msgstr "" + +#: order/models.py:62 +msgid "Order reference" +msgstr "" + +#: order/models.py:64 +msgid "Order description" +msgstr "" + +#: order/models.py:66 +msgid "Link to external page" +msgstr "" + +#: order/models.py:83 +msgid "Order notes" +msgstr "" + +#: order/models.py:125 +msgid "Company" +msgstr "" + +#: order/models.py:156 order/models.py:201 part/views.py:1032 +#: stock/models.py:437 +msgid "Quantity must be greater than zero" +msgstr "" + +#: order/models.py:161 +msgid "Part supplier must match PO supplier" +msgstr "" + +#: order/models.py:196 +msgid "Lines can only be received against an order marked as 'Placed'" +msgstr "" + +#: order/models.py:245 +msgid "Item quantity" +msgstr "" + +#: order/models.py:247 +msgid "Line item reference" +msgstr "" + +#: order/models.py:249 +msgid "Line item notes" +msgstr "" + +#: order/models.py:275 +msgid "Purchase Order" +msgstr "" + +#: order/models.py:284 +msgid "Supplier part" +msgstr "" + +#: order/models.py:287 +msgid "Number of items received" +msgstr "" + +#: order/views.py:140 +msgid "Confirm order cancellation" +msgstr "" + +#: order/views.py:173 +msgid "Confirm order placement" +msgstr "" + +#: order/views.py:695 +msgid "Invalid Purchase Order" +msgstr "" + +#: order/views.py:703 +msgid "Supplier must match for Part and Order" +msgstr "" + +#: order/views.py:708 +msgid "Invalid SupplierPart selection" +msgstr "" + +#: part/bom.py:107 +#, python-brace-format +msgid "Unsupported file format: {f}" +msgstr "" + +#: part/bom.py:112 +msgid "Error reading BOM file (invalid data)" +msgstr "" + +#: part/bom.py:114 +msgid "Error reading BOM file (incorrect row size)" +msgstr "" + +#: part/forms.py:37 +msgid "Confirm that the BOM is correct" +msgstr "" + +#: part/forms.py:49 +msgid "Select BOM file to upload" +msgstr "" + +#: part/forms.py:73 +msgid "Select part category" +msgstr "" + +#: part/forms.py:81 +msgid "Perform 'deep copy' which will duplicate all BOM data for this part" +msgstr "" + +#: part/forms.py:86 +msgid "Confirm part creation" +msgstr "" + +#: part/forms.py:173 +msgid "Input quantity for price calculation" +msgstr "" + +#: part/forms.py:176 +msgid "Select currency for price calculation" +msgstr "" + +#: part/models.py:308 +msgid "Part must be unique for name, IPN and revision" +msgstr "" + +#: part/models.py:322 +msgid "Part cannot be a template part if it is a variant of another part" +msgstr "" + +#: part/models.py:323 +msgid "Part cannot be a variant of another part if it is already a template" +msgstr "" + +#: part/models.py:992 +msgid "Parameter template name must be unique" +msgstr "" + +#: part/models.py:1141 +msgid "Part cannot be added to its own Bill of Materials" +msgstr "" + +#: part/models.py:1148 +#, python-brace-format +msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)" +msgstr "" + +#: part/templates/part/category.html:13 part/templates/part/category.html:69 +msgid "Part Categories" +msgstr "" + +#: part/templates/part/category.html:14 +msgid "All parts" +msgstr "" + +#: part/templates/part/category.html:34 +msgid "Category Details" +msgstr "" + +#: part/templates/part/category.html:37 +msgid "Category Path" +msgstr "" + +#: part/templates/part/category.html:41 +msgid "Category Description" +msgstr "" + +#: part/templates/part/category.html:46 part/templates/part/detail.html:63 +msgid "Default Location" +msgstr "" + +#: part/templates/part/category.html:52 part/templates/part/detail.html:43 +msgid "Keywords" +msgstr "" + +#: part/templates/part/category.html:57 +msgid "Subcategories" +msgstr "" + +#: part/templates/part/category.html:61 +msgid "Parts (Including subcategories)" +msgstr "" + +#: part/templates/part/category.html:66 part/templates/part/detail.html:8 +msgid "Part Details" +msgstr "" + +#: part/templates/part/category.html:73 +msgid "Parts" +msgstr "" + +#: part/templates/part/detail.html:16 +msgid "Part name" +msgstr "" + +#: part/templates/part/detail.html:21 +msgid "IPN" +msgstr "" + +#: part/templates/part/detail.html:27 +msgid "Revision" +msgstr "" + +#: part/templates/part/detail.html:32 +msgid "Description" +msgstr "" + +#: part/templates/part/detail.html:37 +msgid "Variant Of" +msgstr "" + +#: part/templates/part/detail.html:49 +msgid "URL" +msgstr "" + +#: part/templates/part/detail.html:54 +msgid "Category" +msgstr "" + +#: part/templates/part/detail.html:69 +msgid "Default Supplier" +msgstr "" + +#: part/templates/part/detail.html:76 +msgid "Units" +msgstr "" + +#: part/templates/part/detail.html:81 +msgid "Minimum Stock" +msgstr "" + +#: part/templates/part/detail.html:90 +msgid "Virtual" +msgstr "" + +#: part/templates/part/detail.html:93 +msgid "Part is virtual (not a physical part)" +msgstr "" + +#: part/templates/part/detail.html:95 +msgid "Part is not a virtual part" +msgstr "" + +#: part/templates/part/detail.html:99 +msgid "Assembly" +msgstr "" + +#: part/templates/part/detail.html:102 +msgid "Part can be assembled from other parts" +msgstr "" + +#: part/templates/part/detail.html:104 +msgid "Part cannot be assembled from other parts" +msgstr "" + +#: part/templates/part/detail.html:108 +msgid "Component" +msgstr "" + +#: part/templates/part/detail.html:111 +msgid "Part can be used in assemblies" +msgstr "" + +#: part/templates/part/detail.html:113 +msgid "Part cannot be used in assemblies" +msgstr "" + +#: part/templates/part/detail.html:117 +msgid "Trackable" +msgstr "" + +#: part/templates/part/detail.html:120 +msgid "Part stock is tracked by serial number" +msgstr "" + +#: part/templates/part/detail.html:122 +msgid "Part stock is not tracked by serial number" +msgstr "" + +#: part/templates/part/detail.html:126 +msgid "Purchaseable" +msgstr "" + +#: part/templates/part/detail.html:129 part/templates/part/detail.html:131 +msgid "Part can be purchased from external suppliers" +msgstr "" + +#: part/templates/part/detail.html:136 +msgid "Sellable" +msgstr "" + +#: part/templates/part/detail.html:139 +msgid "Part can be sold to customers" +msgstr "" + +#: part/templates/part/detail.html:141 +msgid "Part cannot be sold to customers" +msgstr "" + +#: part/templates/part/detail.html:151 +msgid "Notes" +msgstr "" + +#: part/views.py:196 +#, python-brace-format +msgid "Set category for {n} parts" +msgstr "" + +#: part/views.py:773 +msgid "No BOM file provided" +msgstr "" + +#: part/views.py:1034 +msgid "Enter a valid quantity" +msgstr "" + +#: part/views.py:1058 part/views.py:1061 +msgid "Select valid part" +msgstr "" + +#: part/views.py:1067 +msgid "Duplicate part selected" +msgstr "" + +#: part/views.py:1095 +msgid "Select a part" +msgstr "" + +#: part/views.py:1099 +msgid "Specify quantity" +msgstr "" + +#: stock/forms.py:92 +msgid "File Format" +msgstr "" + +#: stock/forms.py:92 +msgid "Select output file format" +msgstr "" + +#: stock/forms.py:94 +msgid "Include stock items in sub locations" +msgstr "" + +#: stock/forms.py:127 +msgid "Destination stock location" +msgstr "" + +#: stock/forms.py:133 +msgid "Confirm movement of stock items" +msgstr "" + +#: stock/forms.py:135 +msgid "Set the destination as the default location for selected parts" +msgstr "" + +#: stock/models.py:201 +#, python-brace-format +msgid "" +"A stock item with this serial number already exists for template part {part}" +msgstr "" + +#: stock/models.py:206 +msgid "A stock item with this serial number already exists" +msgstr "" + +#: stock/models.py:225 +#, python-brace-format +msgid "Part type ('{pf}') must be {pe}" +msgstr "" + +#: stock/models.py:235 stock/models.py:244 +msgid "Quantity must be 1 for item with a serial number" +msgstr "" + +#: stock/models.py:236 +msgid "Serial number cannot be set if quantity greater than 1" +msgstr "" + +#: stock/models.py:252 +msgid "Stock item cannot be created for a template Part" +msgstr "" + +#: stock/models.py:261 +msgid "Item cannot belong to itself" +msgstr "" + +#: stock/models.py:434 +msgid "Quantity must be integer" +msgstr "" + +#: stock/models.py:440 +#, python-brace-format +msgid "Quantity must not exceed available stock quantity ({n})" +msgstr "" + +#: stock/models.py:443 stock/models.py:446 +msgid "Serial numbers must be a list of integers" +msgstr "" + +#: stock/models.py:449 +msgid "Quantity does not match serial numbers" +msgstr "" + +#: stock/models.py:459 +msgid "Serial numbers already exist: " +msgstr "" + +#: stock/models.py:480 +msgid "Add serial number" +msgstr "" + +#: stock/models.py:483 +#, python-brace-format +msgid "Serialized {n} items" +msgstr "" + +#: stock/templates/stock/location.html:37 +msgid "Location Details" +msgstr "" + +#: stock/templates/stock/location.html:40 +msgid "Location Path" +msgstr "" + +#: stock/templates/stock/location.html:44 +msgid "Location Description" +msgstr "" + +#: stock/templates/stock/location.html:48 +msgid "Sublocations" +msgstr "" + +#: stock/templates/stock/location.html:52 +#: stock/templates/stock/location.html:64 +msgid "Stock Items" +msgstr "" + +#: stock/templates/stock/location.html:57 +msgid "Stock Details" +msgstr "" + +#: stock/templates/stock/location.html:60 +msgid "Stock Locations" +msgstr "" + +#: stock/views.py:399 +msgid "Must enter integer value" +msgstr "" + +#: stock/views.py:404 +msgid "Quantity must be positive" +msgstr "" + +#: stock/views.py:411 +#, python-brace-format +msgid "Quantity must not exceed {x}" +msgstr "" + +#: stock/views.py:419 +msgid "Confirm stock adjustment" +msgstr "" + +#: stock/views.py:487 +#, python-brace-format +msgid "Added stock to {n} items" +msgstr "" + +#: stock/views.py:502 +#, python-brace-format +msgid "Removed stock from {n} items" +msgstr "" + +#: stock/views.py:515 +#, python-brace-format +msgid "Counted stock for {n} items" +msgstr "" + +#: stock/views.py:543 +msgid "No items were moved" +msgstr "" + +#: stock/views.py:546 +#, python-brace-format +msgid "Moved {n} items to {dest}" +msgstr "" + +#: stock/views.py:813 +msgid "Invalid part selection" +msgstr "" + +#: stock/views.py:875 +msgid "Created new stock item" +msgstr "" diff --git a/InvenTree/locale/en/LC_MESSAGES/django.mo b/InvenTree/locale/en/LC_MESSAGES/django.mo new file mode 100644 index 0000000000..71cbdf3e9d Binary files /dev/null and b/InvenTree/locale/en/LC_MESSAGES/django.mo differ diff --git a/InvenTree/locale/en/LC_MESSAGES/django.po b/InvenTree/locale/en/LC_MESSAGES/django.po new file mode 100644 index 0000000000..de659e0f1f --- /dev/null +++ b/InvenTree/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,694 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-09-27 00:12+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: InvenTree/helpers.py:157 order/models.py:158 order/models.py:203 +msgid "Invalid quantity provided" +msgstr "" + +#: InvenTree/helpers.py:160 +msgid "Empty serial number string" +msgstr "" + +#: InvenTree/helpers.py:181 InvenTree/helpers.py:198 +#, python-brace-format +msgid "Duplicate serial: {n}" +msgstr "" + +#: InvenTree/helpers.py:185 InvenTree/helpers.py:188 InvenTree/helpers.py:191 +#: InvenTree/helpers.py:202 +#, python-brace-format +msgid "Invalid group: {g}" +msgstr "" + +#: InvenTree/helpers.py:208 +msgid "No serial numbers found" +msgstr "" + +#: InvenTree/helpers.py:212 +#, python-brace-format +msgid "Number of unique serial number ({s}) must match quantity ({q})" +msgstr "" + +#: InvenTree/settings.py:235 +msgid "English" +msgstr "" + +#: InvenTree/settings.py:236 +msgid "German" +msgstr "" + +#: InvenTree/settings.py:237 +msgid "French" +msgstr "" + +#: InvenTree/settings.py:238 +msgid "Polish" +msgstr "" + +#: InvenTree/status_codes.py:27 InvenTree/status_codes.py:82 +msgid "Pending" +msgstr "" + +#: InvenTree/status_codes.py:28 +msgid "Placed" +msgstr "" + +#: InvenTree/status_codes.py:29 InvenTree/status_codes.py:85 +msgid "Complete" +msgstr "" + +#: InvenTree/status_codes.py:30 InvenTree/status_codes.py:84 +msgid "Cancelled" +msgstr "" + +#: InvenTree/status_codes.py:31 InvenTree/status_codes.py:62 +msgid "Lost" +msgstr "" + +#: InvenTree/status_codes.py:32 +msgid "Returned" +msgstr "" + +#: InvenTree/status_codes.py:58 +msgid "OK" +msgstr "" + +#: InvenTree/status_codes.py:59 +msgid "Attention needed" +msgstr "" + +#: InvenTree/status_codes.py:60 +msgid "Damaged" +msgstr "" + +#: InvenTree/status_codes.py:61 +msgid "Destroyed" +msgstr "" + +#: InvenTree/status_codes.py:83 +msgid "Allocated" +msgstr "" + +#: InvenTree/validators.py:35 +msgid "Invalid character in part name" +msgstr "" + +#: InvenTree/validators.py:44 +#, python-brace-format +msgid "Illegal character in name ({x})" +msgstr "" + +#: InvenTree/validators.py:63 InvenTree/validators.py:79 +msgid "Overage value must not be negative" +msgstr "" + +#: InvenTree/validators.py:81 +msgid "Overage must not exceed 100%" +msgstr "" + +#: InvenTree/validators.py:88 +msgid "Overage must be an integer value or a percentage" +msgstr "" + +#: build/forms.py:36 +msgid "Confirm" +msgstr "" + +#: build/forms.py:53 stock/forms.py:34 +msgid "Enter unique serial numbers (or leave blank)" +msgstr "" + +#: build/forms.py:55 +msgid "Confirm build completion" +msgstr "" + +#: build/models.py:381 +#, python-brace-format +msgid "Selected stock item not found in BOM for part '{p}'" +msgstr "" + +#: build/models.py:384 +#, python-brace-format +msgid "Allocated quantity ({n}) must not exceed available quantity ({q})" +msgstr "" + +#: build/views.py:289 stock/views.py:834 +#, python-brace-format +msgid "The following serial numbers already exist: ({sn})" +msgstr "" + +#: common/models.py:65 +msgid "Settings key (must be unique - case insensitive" +msgstr "" + +#: common/models.py:67 +msgid "Settings value" +msgstr "" + +#: common/models.py:69 +msgid "Settings description" +msgstr "" + +#: common/models.py:82 +msgid "Key string must be unique" +msgstr "" + +#: common/models.py:103 +msgid "Currency Symbol e.g. $" +msgstr "" + +#: common/models.py:105 +msgid "Currency Suffix e.g. AUD" +msgstr "" + +#: common/models.py:107 +msgid "Currency Description" +msgstr "" + +#: common/models.py:109 +msgid "Currency Value" +msgstr "" + +#: common/models.py:111 +msgid "Use this currency as the base currency" +msgstr "" + +#: order/forms.py:21 +msgid "Place order" +msgstr "" + +#: order/forms.py:32 +msgid "Cancel order" +msgstr "" + +#: order/forms.py:43 +msgid "Receive parts to this location" +msgstr "" + +#: order/models.py:62 +msgid "Order reference" +msgstr "" + +#: order/models.py:64 +msgid "Order description" +msgstr "" + +#: order/models.py:66 +msgid "Link to external page" +msgstr "" + +#: order/models.py:83 +msgid "Order notes" +msgstr "" + +#: order/models.py:125 +msgid "Company" +msgstr "" + +#: order/models.py:156 order/models.py:201 part/views.py:1032 +#: stock/models.py:437 +msgid "Quantity must be greater than zero" +msgstr "" + +#: order/models.py:161 +msgid "Part supplier must match PO supplier" +msgstr "" + +#: order/models.py:196 +msgid "Lines can only be received against an order marked as 'Placed'" +msgstr "" + +#: order/models.py:245 +msgid "Item quantity" +msgstr "" + +#: order/models.py:247 +msgid "Line item reference" +msgstr "" + +#: order/models.py:249 +msgid "Line item notes" +msgstr "" + +#: order/models.py:275 +msgid "Purchase Order" +msgstr "" + +#: order/models.py:284 +msgid "Supplier part" +msgstr "" + +#: order/models.py:287 +msgid "Number of items received" +msgstr "" + +#: order/views.py:140 +msgid "Confirm order cancellation" +msgstr "" + +#: order/views.py:173 +msgid "Confirm order placement" +msgstr "" + +#: order/views.py:695 +msgid "Invalid Purchase Order" +msgstr "" + +#: order/views.py:703 +msgid "Supplier must match for Part and Order" +msgstr "" + +#: order/views.py:708 +msgid "Invalid SupplierPart selection" +msgstr "" + +#: part/bom.py:107 +#, python-brace-format +msgid "Unsupported file format: {f}" +msgstr "" + +#: part/bom.py:112 +msgid "Error reading BOM file (invalid data)" +msgstr "" + +#: part/bom.py:114 +msgid "Error reading BOM file (incorrect row size)" +msgstr "" + +#: part/forms.py:37 +msgid "Confirm that the BOM is correct" +msgstr "" + +#: part/forms.py:49 +msgid "Select BOM file to upload" +msgstr "" + +#: part/forms.py:73 +msgid "Select part category" +msgstr "" + +#: part/forms.py:81 +msgid "Perform 'deep copy' which will duplicate all BOM data for this part" +msgstr "" + +#: part/forms.py:86 +msgid "Confirm part creation" +msgstr "" + +#: part/forms.py:173 +msgid "Input quantity for price calculation" +msgstr "" + +#: part/forms.py:176 +msgid "Select currency for price calculation" +msgstr "" + +#: part/models.py:308 +msgid "Part must be unique for name, IPN and revision" +msgstr "" + +#: part/models.py:322 +msgid "Part cannot be a template part if it is a variant of another part" +msgstr "" + +#: part/models.py:323 +msgid "Part cannot be a variant of another part if it is already a template" +msgstr "" + +#: part/models.py:992 +msgid "Parameter template name must be unique" +msgstr "" + +#: part/models.py:1141 +msgid "Part cannot be added to its own Bill of Materials" +msgstr "" + +#: part/models.py:1148 +#, python-brace-format +msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)" +msgstr "" + +#: part/templates/part/category.html:13 part/templates/part/category.html:69 +msgid "Part Categories" +msgstr "" + +#: part/templates/part/category.html:14 +msgid "All parts" +msgstr "" + +#: part/templates/part/category.html:34 +msgid "Category Details" +msgstr "" + +#: part/templates/part/category.html:37 +msgid "Category Path" +msgstr "" + +#: part/templates/part/category.html:41 +msgid "Category Description" +msgstr "" + +#: part/templates/part/category.html:46 part/templates/part/detail.html:63 +msgid "Default Location" +msgstr "" + +#: part/templates/part/category.html:52 part/templates/part/detail.html:43 +msgid "Keywords" +msgstr "" + +#: part/templates/part/category.html:57 +msgid "Subcategories" +msgstr "" + +#: part/templates/part/category.html:61 +msgid "Parts (Including subcategories)" +msgstr "" + +#: part/templates/part/category.html:66 part/templates/part/detail.html:8 +msgid "Part Details" +msgstr "" + +#: part/templates/part/category.html:73 +msgid "Parts" +msgstr "" + +#: part/templates/part/detail.html:16 +msgid "Part name" +msgstr "" + +#: part/templates/part/detail.html:21 +msgid "IPN" +msgstr "" + +#: part/templates/part/detail.html:27 +msgid "Revision" +msgstr "" + +#: part/templates/part/detail.html:32 +msgid "Description" +msgstr "" + +#: part/templates/part/detail.html:37 +msgid "Variant Of" +msgstr "" + +#: part/templates/part/detail.html:49 +msgid "URL" +msgstr "" + +#: part/templates/part/detail.html:54 +msgid "Category" +msgstr "" + +#: part/templates/part/detail.html:69 +msgid "Default Supplier" +msgstr "" + +#: part/templates/part/detail.html:76 +msgid "Units" +msgstr "" + +#: part/templates/part/detail.html:81 +msgid "Minimum Stock" +msgstr "" + +#: part/templates/part/detail.html:90 +msgid "Virtual" +msgstr "" + +#: part/templates/part/detail.html:93 +msgid "Part is virtual (not a physical part)" +msgstr "" + +#: part/templates/part/detail.html:95 +msgid "Part is not a virtual part" +msgstr "" + +#: part/templates/part/detail.html:99 +msgid "Assembly" +msgstr "" + +#: part/templates/part/detail.html:102 +msgid "Part can be assembled from other parts" +msgstr "" + +#: part/templates/part/detail.html:104 +msgid "Part cannot be assembled from other parts" +msgstr "" + +#: part/templates/part/detail.html:108 +msgid "Component" +msgstr "" + +#: part/templates/part/detail.html:111 +msgid "Part can be used in assemblies" +msgstr "" + +#: part/templates/part/detail.html:113 +msgid "Part cannot be used in assemblies" +msgstr "" + +#: part/templates/part/detail.html:117 +msgid "Trackable" +msgstr "" + +#: part/templates/part/detail.html:120 +msgid "Part stock is tracked by serial number" +msgstr "" + +#: part/templates/part/detail.html:122 +msgid "Part stock is not tracked by serial number" +msgstr "" + +#: part/templates/part/detail.html:126 +msgid "Purchaseable" +msgstr "" + +#: part/templates/part/detail.html:129 part/templates/part/detail.html:131 +msgid "Part can be purchased from external suppliers" +msgstr "" + +#: part/templates/part/detail.html:136 +msgid "Sellable" +msgstr "" + +#: part/templates/part/detail.html:139 +msgid "Part can be sold to customers" +msgstr "" + +#: part/templates/part/detail.html:141 +msgid "Part cannot be sold to customers" +msgstr "" + +#: part/templates/part/detail.html:151 +msgid "Notes" +msgstr "" + +#: part/views.py:196 +#, python-brace-format +msgid "Set category for {n} parts" +msgstr "" + +#: part/views.py:773 +msgid "No BOM file provided" +msgstr "" + +#: part/views.py:1034 +msgid "Enter a valid quantity" +msgstr "" + +#: part/views.py:1058 part/views.py:1061 +msgid "Select valid part" +msgstr "" + +#: part/views.py:1067 +msgid "Duplicate part selected" +msgstr "" + +#: part/views.py:1095 +msgid "Select a part" +msgstr "" + +#: part/views.py:1099 +msgid "Specify quantity" +msgstr "" + +#: stock/forms.py:92 +msgid "File Format" +msgstr "" + +#: stock/forms.py:92 +msgid "Select output file format" +msgstr "" + +#: stock/forms.py:94 +msgid "Include stock items in sub locations" +msgstr "" + +#: stock/forms.py:127 +msgid "Destination stock location" +msgstr "" + +#: stock/forms.py:133 +msgid "Confirm movement of stock items" +msgstr "" + +#: stock/forms.py:135 +msgid "Set the destination as the default location for selected parts" +msgstr "" + +#: stock/models.py:201 +#, python-brace-format +msgid "" +"A stock item with this serial number already exists for template part {part}" +msgstr "" + +#: stock/models.py:206 +msgid "A stock item with this serial number already exists" +msgstr "" + +#: stock/models.py:225 +#, python-brace-format +msgid "Part type ('{pf}') must be {pe}" +msgstr "" + +#: stock/models.py:235 stock/models.py:244 +msgid "Quantity must be 1 for item with a serial number" +msgstr "" + +#: stock/models.py:236 +msgid "Serial number cannot be set if quantity greater than 1" +msgstr "" + +#: stock/models.py:252 +msgid "Stock item cannot be created for a template Part" +msgstr "" + +#: stock/models.py:261 +msgid "Item cannot belong to itself" +msgstr "" + +#: stock/models.py:434 +msgid "Quantity must be integer" +msgstr "" + +#: stock/models.py:440 +#, python-brace-format +msgid "Quantity must not exceed available stock quantity ({n})" +msgstr "" + +#: stock/models.py:443 stock/models.py:446 +msgid "Serial numbers must be a list of integers" +msgstr "" + +#: stock/models.py:449 +msgid "Quantity does not match serial numbers" +msgstr "" + +#: stock/models.py:459 +msgid "Serial numbers already exist: " +msgstr "" + +#: stock/models.py:480 +msgid "Add serial number" +msgstr "" + +#: stock/models.py:483 +#, python-brace-format +msgid "Serialized {n} items" +msgstr "" + +#: stock/templates/stock/location.html:37 +msgid "Location Details" +msgstr "" + +#: stock/templates/stock/location.html:40 +msgid "Location Path" +msgstr "" + +#: stock/templates/stock/location.html:44 +msgid "Location Description" +msgstr "" + +#: stock/templates/stock/location.html:48 +msgid "Sublocations" +msgstr "" + +#: stock/templates/stock/location.html:52 +#: stock/templates/stock/location.html:64 +msgid "Stock Items" +msgstr "" + +#: stock/templates/stock/location.html:57 +msgid "Stock Details" +msgstr "" + +#: stock/templates/stock/location.html:60 +msgid "Stock Locations" +msgstr "" + +#: stock/views.py:399 +msgid "Must enter integer value" +msgstr "" + +#: stock/views.py:404 +msgid "Quantity must be positive" +msgstr "" + +#: stock/views.py:411 +#, python-brace-format +msgid "Quantity must not exceed {x}" +msgstr "" + +#: stock/views.py:419 +msgid "Confirm stock adjustment" +msgstr "" + +#: stock/views.py:487 +#, python-brace-format +msgid "Added stock to {n} items" +msgstr "" + +#: stock/views.py:502 +#, python-brace-format +msgid "Removed stock from {n} items" +msgstr "" + +#: stock/views.py:515 +#, python-brace-format +msgid "Counted stock for {n} items" +msgstr "" + +#: stock/views.py:543 +msgid "No items were moved" +msgstr "" + +#: stock/views.py:546 +#, python-brace-format +msgid "Moved {n} items to {dest}" +msgstr "" + +#: stock/views.py:813 +msgid "Invalid part selection" +msgstr "" + +#: stock/views.py:875 +msgid "Created new stock item" +msgstr "" diff --git a/InvenTree/locale/es/LC_MESSAGES/django.mo b/InvenTree/locale/es/LC_MESSAGES/django.mo new file mode 100644 index 0000000000..71cbdf3e9d Binary files /dev/null and b/InvenTree/locale/es/LC_MESSAGES/django.mo differ diff --git a/InvenTree/locale/es/LC_MESSAGES/django.po b/InvenTree/locale/es/LC_MESSAGES/django.po new file mode 100644 index 0000000000..de659e0f1f --- /dev/null +++ b/InvenTree/locale/es/LC_MESSAGES/django.po @@ -0,0 +1,694 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-09-27 00:12+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: InvenTree/helpers.py:157 order/models.py:158 order/models.py:203 +msgid "Invalid quantity provided" +msgstr "" + +#: InvenTree/helpers.py:160 +msgid "Empty serial number string" +msgstr "" + +#: InvenTree/helpers.py:181 InvenTree/helpers.py:198 +#, python-brace-format +msgid "Duplicate serial: {n}" +msgstr "" + +#: InvenTree/helpers.py:185 InvenTree/helpers.py:188 InvenTree/helpers.py:191 +#: InvenTree/helpers.py:202 +#, python-brace-format +msgid "Invalid group: {g}" +msgstr "" + +#: InvenTree/helpers.py:208 +msgid "No serial numbers found" +msgstr "" + +#: InvenTree/helpers.py:212 +#, python-brace-format +msgid "Number of unique serial number ({s}) must match quantity ({q})" +msgstr "" + +#: InvenTree/settings.py:235 +msgid "English" +msgstr "" + +#: InvenTree/settings.py:236 +msgid "German" +msgstr "" + +#: InvenTree/settings.py:237 +msgid "French" +msgstr "" + +#: InvenTree/settings.py:238 +msgid "Polish" +msgstr "" + +#: InvenTree/status_codes.py:27 InvenTree/status_codes.py:82 +msgid "Pending" +msgstr "" + +#: InvenTree/status_codes.py:28 +msgid "Placed" +msgstr "" + +#: InvenTree/status_codes.py:29 InvenTree/status_codes.py:85 +msgid "Complete" +msgstr "" + +#: InvenTree/status_codes.py:30 InvenTree/status_codes.py:84 +msgid "Cancelled" +msgstr "" + +#: InvenTree/status_codes.py:31 InvenTree/status_codes.py:62 +msgid "Lost" +msgstr "" + +#: InvenTree/status_codes.py:32 +msgid "Returned" +msgstr "" + +#: InvenTree/status_codes.py:58 +msgid "OK" +msgstr "" + +#: InvenTree/status_codes.py:59 +msgid "Attention needed" +msgstr "" + +#: InvenTree/status_codes.py:60 +msgid "Damaged" +msgstr "" + +#: InvenTree/status_codes.py:61 +msgid "Destroyed" +msgstr "" + +#: InvenTree/status_codes.py:83 +msgid "Allocated" +msgstr "" + +#: InvenTree/validators.py:35 +msgid "Invalid character in part name" +msgstr "" + +#: InvenTree/validators.py:44 +#, python-brace-format +msgid "Illegal character in name ({x})" +msgstr "" + +#: InvenTree/validators.py:63 InvenTree/validators.py:79 +msgid "Overage value must not be negative" +msgstr "" + +#: InvenTree/validators.py:81 +msgid "Overage must not exceed 100%" +msgstr "" + +#: InvenTree/validators.py:88 +msgid "Overage must be an integer value or a percentage" +msgstr "" + +#: build/forms.py:36 +msgid "Confirm" +msgstr "" + +#: build/forms.py:53 stock/forms.py:34 +msgid "Enter unique serial numbers (or leave blank)" +msgstr "" + +#: build/forms.py:55 +msgid "Confirm build completion" +msgstr "" + +#: build/models.py:381 +#, python-brace-format +msgid "Selected stock item not found in BOM for part '{p}'" +msgstr "" + +#: build/models.py:384 +#, python-brace-format +msgid "Allocated quantity ({n}) must not exceed available quantity ({q})" +msgstr "" + +#: build/views.py:289 stock/views.py:834 +#, python-brace-format +msgid "The following serial numbers already exist: ({sn})" +msgstr "" + +#: common/models.py:65 +msgid "Settings key (must be unique - case insensitive" +msgstr "" + +#: common/models.py:67 +msgid "Settings value" +msgstr "" + +#: common/models.py:69 +msgid "Settings description" +msgstr "" + +#: common/models.py:82 +msgid "Key string must be unique" +msgstr "" + +#: common/models.py:103 +msgid "Currency Symbol e.g. $" +msgstr "" + +#: common/models.py:105 +msgid "Currency Suffix e.g. AUD" +msgstr "" + +#: common/models.py:107 +msgid "Currency Description" +msgstr "" + +#: common/models.py:109 +msgid "Currency Value" +msgstr "" + +#: common/models.py:111 +msgid "Use this currency as the base currency" +msgstr "" + +#: order/forms.py:21 +msgid "Place order" +msgstr "" + +#: order/forms.py:32 +msgid "Cancel order" +msgstr "" + +#: order/forms.py:43 +msgid "Receive parts to this location" +msgstr "" + +#: order/models.py:62 +msgid "Order reference" +msgstr "" + +#: order/models.py:64 +msgid "Order description" +msgstr "" + +#: order/models.py:66 +msgid "Link to external page" +msgstr "" + +#: order/models.py:83 +msgid "Order notes" +msgstr "" + +#: order/models.py:125 +msgid "Company" +msgstr "" + +#: order/models.py:156 order/models.py:201 part/views.py:1032 +#: stock/models.py:437 +msgid "Quantity must be greater than zero" +msgstr "" + +#: order/models.py:161 +msgid "Part supplier must match PO supplier" +msgstr "" + +#: order/models.py:196 +msgid "Lines can only be received against an order marked as 'Placed'" +msgstr "" + +#: order/models.py:245 +msgid "Item quantity" +msgstr "" + +#: order/models.py:247 +msgid "Line item reference" +msgstr "" + +#: order/models.py:249 +msgid "Line item notes" +msgstr "" + +#: order/models.py:275 +msgid "Purchase Order" +msgstr "" + +#: order/models.py:284 +msgid "Supplier part" +msgstr "" + +#: order/models.py:287 +msgid "Number of items received" +msgstr "" + +#: order/views.py:140 +msgid "Confirm order cancellation" +msgstr "" + +#: order/views.py:173 +msgid "Confirm order placement" +msgstr "" + +#: order/views.py:695 +msgid "Invalid Purchase Order" +msgstr "" + +#: order/views.py:703 +msgid "Supplier must match for Part and Order" +msgstr "" + +#: order/views.py:708 +msgid "Invalid SupplierPart selection" +msgstr "" + +#: part/bom.py:107 +#, python-brace-format +msgid "Unsupported file format: {f}" +msgstr "" + +#: part/bom.py:112 +msgid "Error reading BOM file (invalid data)" +msgstr "" + +#: part/bom.py:114 +msgid "Error reading BOM file (incorrect row size)" +msgstr "" + +#: part/forms.py:37 +msgid "Confirm that the BOM is correct" +msgstr "" + +#: part/forms.py:49 +msgid "Select BOM file to upload" +msgstr "" + +#: part/forms.py:73 +msgid "Select part category" +msgstr "" + +#: part/forms.py:81 +msgid "Perform 'deep copy' which will duplicate all BOM data for this part" +msgstr "" + +#: part/forms.py:86 +msgid "Confirm part creation" +msgstr "" + +#: part/forms.py:173 +msgid "Input quantity for price calculation" +msgstr "" + +#: part/forms.py:176 +msgid "Select currency for price calculation" +msgstr "" + +#: part/models.py:308 +msgid "Part must be unique for name, IPN and revision" +msgstr "" + +#: part/models.py:322 +msgid "Part cannot be a template part if it is a variant of another part" +msgstr "" + +#: part/models.py:323 +msgid "Part cannot be a variant of another part if it is already a template" +msgstr "" + +#: part/models.py:992 +msgid "Parameter template name must be unique" +msgstr "" + +#: part/models.py:1141 +msgid "Part cannot be added to its own Bill of Materials" +msgstr "" + +#: part/models.py:1148 +#, python-brace-format +msgid "Part '{p1}' is used in BOM for '{p2}' (recursive)" +msgstr "" + +#: part/templates/part/category.html:13 part/templates/part/category.html:69 +msgid "Part Categories" +msgstr "" + +#: part/templates/part/category.html:14 +msgid "All parts" +msgstr "" + +#: part/templates/part/category.html:34 +msgid "Category Details" +msgstr "" + +#: part/templates/part/category.html:37 +msgid "Category Path" +msgstr "" + +#: part/templates/part/category.html:41 +msgid "Category Description" +msgstr "" + +#: part/templates/part/category.html:46 part/templates/part/detail.html:63 +msgid "Default Location" +msgstr "" + +#: part/templates/part/category.html:52 part/templates/part/detail.html:43 +msgid "Keywords" +msgstr "" + +#: part/templates/part/category.html:57 +msgid "Subcategories" +msgstr "" + +#: part/templates/part/category.html:61 +msgid "Parts (Including subcategories)" +msgstr "" + +#: part/templates/part/category.html:66 part/templates/part/detail.html:8 +msgid "Part Details" +msgstr "" + +#: part/templates/part/category.html:73 +msgid "Parts" +msgstr "" + +#: part/templates/part/detail.html:16 +msgid "Part name" +msgstr "" + +#: part/templates/part/detail.html:21 +msgid "IPN" +msgstr "" + +#: part/templates/part/detail.html:27 +msgid "Revision" +msgstr "" + +#: part/templates/part/detail.html:32 +msgid "Description" +msgstr "" + +#: part/templates/part/detail.html:37 +msgid "Variant Of" +msgstr "" + +#: part/templates/part/detail.html:49 +msgid "URL" +msgstr "" + +#: part/templates/part/detail.html:54 +msgid "Category" +msgstr "" + +#: part/templates/part/detail.html:69 +msgid "Default Supplier" +msgstr "" + +#: part/templates/part/detail.html:76 +msgid "Units" +msgstr "" + +#: part/templates/part/detail.html:81 +msgid "Minimum Stock" +msgstr "" + +#: part/templates/part/detail.html:90 +msgid "Virtual" +msgstr "" + +#: part/templates/part/detail.html:93 +msgid "Part is virtual (not a physical part)" +msgstr "" + +#: part/templates/part/detail.html:95 +msgid "Part is not a virtual part" +msgstr "" + +#: part/templates/part/detail.html:99 +msgid "Assembly" +msgstr "" + +#: part/templates/part/detail.html:102 +msgid "Part can be assembled from other parts" +msgstr "" + +#: part/templates/part/detail.html:104 +msgid "Part cannot be assembled from other parts" +msgstr "" + +#: part/templates/part/detail.html:108 +msgid "Component" +msgstr "" + +#: part/templates/part/detail.html:111 +msgid "Part can be used in assemblies" +msgstr "" + +#: part/templates/part/detail.html:113 +msgid "Part cannot be used in assemblies" +msgstr "" + +#: part/templates/part/detail.html:117 +msgid "Trackable" +msgstr "" + +#: part/templates/part/detail.html:120 +msgid "Part stock is tracked by serial number" +msgstr "" + +#: part/templates/part/detail.html:122 +msgid "Part stock is not tracked by serial number" +msgstr "" + +#: part/templates/part/detail.html:126 +msgid "Purchaseable" +msgstr "" + +#: part/templates/part/detail.html:129 part/templates/part/detail.html:131 +msgid "Part can be purchased from external suppliers" +msgstr "" + +#: part/templates/part/detail.html:136 +msgid "Sellable" +msgstr "" + +#: part/templates/part/detail.html:139 +msgid "Part can be sold to customers" +msgstr "" + +#: part/templates/part/detail.html:141 +msgid "Part cannot be sold to customers" +msgstr "" + +#: part/templates/part/detail.html:151 +msgid "Notes" +msgstr "" + +#: part/views.py:196 +#, python-brace-format +msgid "Set category for {n} parts" +msgstr "" + +#: part/views.py:773 +msgid "No BOM file provided" +msgstr "" + +#: part/views.py:1034 +msgid "Enter a valid quantity" +msgstr "" + +#: part/views.py:1058 part/views.py:1061 +msgid "Select valid part" +msgstr "" + +#: part/views.py:1067 +msgid "Duplicate part selected" +msgstr "" + +#: part/views.py:1095 +msgid "Select a part" +msgstr "" + +#: part/views.py:1099 +msgid "Specify quantity" +msgstr "" + +#: stock/forms.py:92 +msgid "File Format" +msgstr "" + +#: stock/forms.py:92 +msgid "Select output file format" +msgstr "" + +#: stock/forms.py:94 +msgid "Include stock items in sub locations" +msgstr "" + +#: stock/forms.py:127 +msgid "Destination stock location" +msgstr "" + +#: stock/forms.py:133 +msgid "Confirm movement of stock items" +msgstr "" + +#: stock/forms.py:135 +msgid "Set the destination as the default location for selected parts" +msgstr "" + +#: stock/models.py:201 +#, python-brace-format +msgid "" +"A stock item with this serial number already exists for template part {part}" +msgstr "" + +#: stock/models.py:206 +msgid "A stock item with this serial number already exists" +msgstr "" + +#: stock/models.py:225 +#, python-brace-format +msgid "Part type ('{pf}') must be {pe}" +msgstr "" + +#: stock/models.py:235 stock/models.py:244 +msgid "Quantity must be 1 for item with a serial number" +msgstr "" + +#: stock/models.py:236 +msgid "Serial number cannot be set if quantity greater than 1" +msgstr "" + +#: stock/models.py:252 +msgid "Stock item cannot be created for a template Part" +msgstr "" + +#: stock/models.py:261 +msgid "Item cannot belong to itself" +msgstr "" + +#: stock/models.py:434 +msgid "Quantity must be integer" +msgstr "" + +#: stock/models.py:440 +#, python-brace-format +msgid "Quantity must not exceed available stock quantity ({n})" +msgstr "" + +#: stock/models.py:443 stock/models.py:446 +msgid "Serial numbers must be a list of integers" +msgstr "" + +#: stock/models.py:449 +msgid "Quantity does not match serial numbers" +msgstr "" + +#: stock/models.py:459 +msgid "Serial numbers already exist: " +msgstr "" + +#: stock/models.py:480 +msgid "Add serial number" +msgstr "" + +#: stock/models.py:483 +#, python-brace-format +msgid "Serialized {n} items" +msgstr "" + +#: stock/templates/stock/location.html:37 +msgid "Location Details" +msgstr "" + +#: stock/templates/stock/location.html:40 +msgid "Location Path" +msgstr "" + +#: stock/templates/stock/location.html:44 +msgid "Location Description" +msgstr "" + +#: stock/templates/stock/location.html:48 +msgid "Sublocations" +msgstr "" + +#: stock/templates/stock/location.html:52 +#: stock/templates/stock/location.html:64 +msgid "Stock Items" +msgstr "" + +#: stock/templates/stock/location.html:57 +msgid "Stock Details" +msgstr "" + +#: stock/templates/stock/location.html:60 +msgid "Stock Locations" +msgstr "" + +#: stock/views.py:399 +msgid "Must enter integer value" +msgstr "" + +#: stock/views.py:404 +msgid "Quantity must be positive" +msgstr "" + +#: stock/views.py:411 +#, python-brace-format +msgid "Quantity must not exceed {x}" +msgstr "" + +#: stock/views.py:419 +msgid "Confirm stock adjustment" +msgstr "" + +#: stock/views.py:487 +#, python-brace-format +msgid "Added stock to {n} items" +msgstr "" + +#: stock/views.py:502 +#, python-brace-format +msgid "Removed stock from {n} items" +msgstr "" + +#: stock/views.py:515 +#, python-brace-format +msgid "Counted stock for {n} items" +msgstr "" + +#: stock/views.py:543 +msgid "No items were moved" +msgstr "" + +#: stock/views.py:546 +#, python-brace-format +msgid "Moved {n} items to {dest}" +msgstr "" + +#: stock/views.py:813 +msgid "Invalid part selection" +msgstr "" + +#: stock/views.py:875 +msgid "Created new stock item" +msgstr "" diff --git a/InvenTree/part/templates/part/category.html b/InvenTree/part/templates/part/category.html index f6c49b2094..90c0503783 100644 --- a/InvenTree/part/templates/part/category.html +++ b/InvenTree/part/templates/part/category.html @@ -1,5 +1,6 @@ {% extends "part/part_app_base.html" %} {% load static %} +{% load i18n %} {% block content %} @@ -9,8 +10,8 @@

{{ category.name }}

{{ category.description }}

{% else %} -

Part Categories

-

All parts

+

{% trans "Part Categories" %}

+

{% trans "All parts" %}

{% endif %}

@@ -30,37 +31,49 @@
{% if category %} -

Category Details

+

{% trans "Category Details" %}

- + - + {% if category.default_location %} - + {% endif %} {% if category.default_keywords %} - + {% endif %} - + - +
Category Path{% trans "Category Path" %} {{ category.pathstring }}
Category Description{% trans "Category Description" %} {{ category.description }}
Default Location{% trans "Default Location" %} {{ category.default_location.pathstring }}
Keywords{% trans "Keywords" %} {{ category.default_keywords }}
Subcategories{% trans "Subcategories" %} {{ category.children.count }}
Parts (Including subcategories){% trans "Parts (Including subcategories)" %} {{ category.partcount }}
+ {% else %} +

{% trans "Part Details" %}

+ + + + + + + + + +
{% trans "Part Categories" %}{{ category_count }}
{% trans "Parts" %}{{ part_count }}
{% endif %}
diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index d8bb40d792..47c530fb84 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -1,10 +1,11 @@ {% extends "part/part_base.html" %} {% load static %} +{% load i18n %} {% block details %} {% include 'part/tabs.html' with tab='detail' %} -

Part Details

+

{% trans "Part Details" %}


@@ -12,45 +13,45 @@
- + {% if part.IPN %} - + {% endif %} {% if part.revision %} - + {% endif %} - + {% if part.variant_of %} - + {% endif %} {% if part.keywords %} - + {% endif %} {% if part.URL %} - + {% endif %} - + {% if part.default_location %} - + {% endif %} {% if part.default_supplier %} - + {% endif %} - + {% if part.minimum_stock > 0 %} - + {% endif %} @@ -86,58 +87,58 @@
Part name{% trans "Part name" %} {{ part.name }}
IPN{% trans "IPN" %} {{ part.IPN }}
Revision{% trans "Revision" %} {{ part.revision }}
Description{% trans "Description" %} {{ part.description }}
Variant Of{% trans "Variant Of" %} {{ part.variant_of.full_name }}
Keywords{% trans "Keywords" %} {{ part.keywords }}
URL{% trans "URL" %} {{ part.URL }}
Category{% trans "Category" %} {% if part.category %} {{ part.category.pathstring }} @@ -59,25 +60,25 @@
Default Location{% trans "Default Location" %} {{ part.default_location.pathstring }}
Default Supplier{% trans "Default Supplier" %} {{ part.default_supplier.supplier.name }} | {{ part.default_supplier.SKU }}
Units{% trans "Units" %} {{ part.units }}
Minimum Stock{% trans "Minimum Stock" %} {{ part.minimum_stock }}
- + {% if part.virtual %} - + {% else %} - + {% endif %} - + {% if part.assembly %} - + {% else %} - + {% endif %} - + {% if part.component %} - + {% else %} - + {% endif %} - + {% if part.trackable %} - + {% else %} - + {% endif %} - + {% if part.purchaseable %} - + {% else %} - + {% endif %} {% if 0 %} - + {% if part.salable %} - + {% else %} - + {% endif %} {% endif %} @@ -147,7 +148,7 @@ {% if part.notes %}
-
Notes
+
{% trans "Notes" %}
{{ part.notes }}
{% endif %} diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index a736b9275e..6d4eb5203b 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -56,6 +56,8 @@ class PartIndex(ListView): children = PartCategory.objects.filter(parent=None) context['children'] = children + context['category_count'] = PartCategory.objects.count() + context['part_count'] = Part.objects.count() return context diff --git a/InvenTree/stock/templates/stock/location.html b/InvenTree/stock/templates/stock/location.html index 45743bbd5d..baa7511726 100644 --- a/InvenTree/stock/templates/stock/location.html +++ b/InvenTree/stock/templates/stock/location.html @@ -1,5 +1,6 @@ {% extends "stock/stock_app_base.html" %} {% load static %} +{% load i18n %} {% block content %}
@@ -33,25 +34,37 @@
{% if location %} -

Location Details

+

{% trans "Location Details" %}

Virtual{% trans "Virtual" %} {% include "slide.html" with state=part.virtual field='virtual' %}Part is virtual (not a physical part){% trans "Part is virtual (not a physical part)" %}Part is not a virtual part{% trans "Part is not a virtual part" %}
Assembly{% trans "Assembly" %} {% include "slide.html" with state=part.assembly field='assembly' %}Part can be assembled from other parts{% trans "Part can be assembled from other parts" %}Part cannot be assembled from other parts{% trans "Part cannot be assembled from other parts" %}
Component{% trans "Component" %} {% include "slide.html" with state=part.component field='component' %}Part can be used in assemblies{% trans "Part can be used in assemblies" %}Part cannot be used in assemblies{% trans "Part cannot be used in assemblies" %}
Trackable{% trans "Trackable" %} {% include "slide.html" with state=part.trackable field='trackable' %}Part stock is tracked by serial number{% trans "Part stock is tracked by serial number" %}Part stock is not tracked by serial number{% trans "Part stock is not tracked by serial number" %}
Purchaseable{% trans "Purchaseable" %} {% include "slide.html" with state=part.purchaseable field='purchaseable' %}Part can be purchased from external suppliers{% trans "Part can be purchased from external suppliers" %}Part can be purchased from external suppliers{% trans "Part can be purchased from external suppliers" %}
Sellable{% trans "Sellable" %} {% include "slide.html" with state=part.salable field='salable' %}Part can be sold to customers{% trans "Part can be sold to customers" %}Part cannot be sold to customers{% trans "Part cannot be sold to customers" %}
- + - + - + - +
Location Path{% trans "Location Path" %} {{ location.pathstring }}
Location Description{% trans "Location Description" %} {{ location.description }}
Sublocations{% trans "Sublocations" %} {{ location.children.count }}
Stock Items{% trans "Stock Items" %} {{ location.item_count }}
+ {% else %} +

{% trans "Stock Details" %}

+ + + + + + + + + +
{% trans "Stock Locations" %}{{ loc_count }}
{% trans "Stock Items" %}{{ stock_count }}
{% endif %}
diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index d8833d59d0..f7bbe9ad21 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -53,6 +53,9 @@ class StockIndex(ListView): context['locations'] = locations context['items'] = StockItem.objects.all() + context['loc_count'] = StockLocation.objects.count() + context['stock_count'] = StockItem.objects.count() + return context diff --git a/InvenTree/templates/base.html b/InvenTree/templates/base.html index 51f396df8b..94fb3bc44b 100644 --- a/InvenTree/templates/base.html +++ b/InvenTree/templates/base.html @@ -113,14 +113,8 @@ $(document).ready(function () { {% block js_ready %} {% endblock %} - /* Run document-ready scripts. - * Ref: static/script/inventree/inventree.js - */ inventreeDocReady(); - /* Display any cached alert messages - * Ref: static/script/inventree/notification.js - */ showCachedAlerts(); }); diff --git a/Makefile b/Makefile index ebbd9cafef..08318220ac 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,11 @@ postgresql: apt-get install postgresql postgresql-contrib libpq-dev pip3 install psycopg2 +# Update translation files +translate: + cd InvenTree && python3 manage.py makemessages + cd InvenTree && python3 manage.py compilemessages + # Run PEP style checks against source code style: flake8 InvenTree @@ -67,4 +72,4 @@ backup: cd InvenTree && python3 manage.py dbbackup cd InvenTree && python3 manage.py mediabackup -.PHONY: clean migrate superuser install mysql postgresql static style test coverage docreqs docs backup update \ No newline at end of file +.PHONY: clean migrate superuser install mysql postgresql translate static style test coverage docreqs docs backup update \ No newline at end of file diff --git a/README.md b/README.md index 962f8186c9..1b2996f80c 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ However, powerful business logic works in the background to ensure that stock tr For InvenTree documentation, refer to the [user documentation](https://inventree.github.io). -## Code Documentation +## Developer Documentation -For project code documentation, refer to the [developer documentation](http://inventree.readthedocs.io/en/latest/) (auto-generated) +For site administrator and project code documentation, refer to the [developer documentation](http://inventree.readthedocs.io/en/latest/). This includes auto-generated documentation of the InvenTree python codebase. ## Getting Started diff --git a/ci/check_locale_files.py b/ci/check_locale_files.py new file mode 100644 index 0000000000..9995ceaec5 --- /dev/null +++ b/ci/check_locale_files.py @@ -0,0 +1,30 @@ +""" Check that there are no database migration files which have not been committed. """ + +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import sys +import subprocess + +print("Checking for uncommitted locale files...") + +cmd = ['git', 'status'] + +proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + +out, err = proc.communicate() + +locales = [] + +for line in str(out.decode()).split('\n'): + # Check for any compiled translation files that have not been committed + if 'modified:' in line and '/locale/' in line and 'django.po' in line: + locales.append(line) + +if len(locales) > 0: + print("There are {n} unstaged locale files:".format(n=len(locales))) + + for l in locales: + print(" - {l}".format(l=l)) + +sys.exit(len(locales)) \ No newline at end of file diff --git a/ci/check_migration_files.py b/ci/check_migration_files.py index 5d0da79142..88e5a90bee 100644 --- a/ci/check_migration_files.py +++ b/ci/check_migration_files.py @@ -6,6 +6,8 @@ from __future__ import unicode_literals import sys import subprocess +print("Checking for unstaged migration files...") + cmd = ['git', 'ls-files', '--exclude-standard', '--others'] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) diff --git a/docs/index.rst b/docs/index.rst index b5547c2520..ddee7f2b6a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,6 +12,7 @@ InvenTree Source Documentation Deployment Migrate Data Update InvenTree + Translations Backup and Restore Modal Forms Tables diff --git a/docs/start.rst b/docs/start.rst index a5621d9f97..f45c3a8d14 100644 --- a/docs/start.rst +++ b/docs/start.rst @@ -17,9 +17,11 @@ Requirements To install InvenTree you will need the following system components installed: * python3 -* python3-pip +* python3-pip (pip3) * make +Each of these programs need to be installed (e.g. using apt or similar) before running the ``make install`` script. + Installation ------------ @@ -88,6 +90,7 @@ Other shorthand functions are provided for the development and testing process: * ``make migrate`` - Perform database migrations * ``make mysql`` - Install packages required for MySQL database backend * ``make postgresql`` - Install packages required for PostgreSQL database backend +* ``make translate`` - Compile language translation files (requires gettext system package) * ``make backup`` - Backup database tables and media files * ``make test`` - Run all unit tests * ``make coverage`` - Run all unit tests and generate code coverage report diff --git a/docs/translate.rst b/docs/translate.rst new file mode 100644 index 0000000000..ba62dc6780 --- /dev/null +++ b/docs/translate.rst @@ -0,0 +1,16 @@ +Translations +============ + +.. toctree:: + :titlesonly: + :maxdepth: 2 + :caption: Language Translation + :hidden: + +InvenTree supports multi-language translation using the `Django Translation Framework `_ + +Translation strings are located in the `InvenTree/locales/` directory, and translation files can be easily added here. + +To set the default language, change the `language` setting in the `config.yaml` settings file. + +To recompile the translation files (after adding new translation strings), run the command ``make translate`` from the root directory.