From ab82d6e4eb6304bad2adeaff0b41002d545d4112 Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Sun, 26 Dec 2021 13:36:12 -0800 Subject: [PATCH] Web Interface i18n (#564) * Add flask_babel 2.0.0 to requirements * Partial i18n * Use current locale for protobuf requests * Don't store generated messages.pot in git * Internationalize all python code * Formatting fixes * Partial internationalization of html * Iterate on html i18n * Completed i18n of code * Improve i18n of strings * Blurb about i18n in the readme * Improve i18n strings * Add the compiled messages.mo files to .gitignore * Add complete Swedish localization * Generate localizations in start.sh * Only compile messages.mo in start.sh; better sequence * Fix bug --- .gitignore | 2 + src/web/README.md | 12 + src/web/babel.cfg | 3 + src/web/file_cmds.py | 84 +- src/web/pi_cmds.py | 3 +- src/web/ractl_cmds.py | 24 +- src/web/requirements.txt | 1 + src/web/socket_cmds.py | 27 +- src/web/start.sh | 2 + src/web/templates/base.html | 18 +- src/web/templates/drives.html | 66 +- src/web/templates/index.html | 256 ++-- .../translations/sv/LC_MESSAGES/messages.po | 1127 +++++++++++++++++ src/web/web.py | 157 ++- 14 files changed, 1514 insertions(+), 268 deletions(-) create mode 100644 src/web/babel.cfg create mode 100644 src/web/translations/sv/LC_MESSAGES/messages.po diff --git a/.gitignore b/.gitignore index a9d4c876..6226875f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ src/oled_monitor/current src/oled_monitor/rascsi_interface_pb2.py src/raspberrypi/hfdisk/ *~ +messages.pot +messages.mo diff --git a/src/web/README.md b/src/web/README.md index 8a7a79f3..b5c61325 100644 --- a/src/web/README.md +++ b/src/web/README.md @@ -46,3 +46,15 @@ $ cd ~/source/RASCSI $ git remote add pi ssh://pi@rascsi/home/pi/dev.git $ git push pi master ``` + +## Localizing the Web Interface + +We use the Flask-Babel library and Flask/Jinja2 extension for i18n. + +To create a new localization, it needs to be added to accept_languages in +the get_locale() method, and also to localizer.cpp in the RaSCSI C++ code. + +Once this is done, follow the steps in the [Flask-Babel documentation](https://flask-babel.tkte.ch/#translating-applications) +to generate the messages.po for the new language. + +Updating an existing messages.po is also covered above. diff --git a/src/web/babel.cfg b/src/web/babel.cfg new file mode 100644 index 00000000..f0234b32 --- /dev/null +++ b/src/web/babel.cfg @@ -0,0 +1,3 @@ +[python: **.py] +[jinja2: **/templates/**.html] +extensions=jinja2.ext.autoescape,jinja2.ext.with_ diff --git a/src/web/file_cmds.py b/src/web/file_cmds.py index fb1469a0..84791959 100644 --- a/src/web/file_cmds.py +++ b/src/web/file_cmds.py @@ -6,6 +6,7 @@ import os import logging from pathlib import PurePath from flask import current_app +from flask_babel import _ from ractl_cmds import ( get_server_info, @@ -65,6 +66,7 @@ def list_images(): command = proto.PbCommand() command.operation = proto.PbOperation.DEFAULT_IMAGE_FILES_INFO command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] data = send_pb_command(command.SerializeToString()) result = proto.PbResult() @@ -121,6 +123,7 @@ def create_new_image(file_name, file_type, size): command = proto.PbCommand() command.operation = proto.PbOperation.CREATE_IMAGE command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] command.params["file"] = file_name + "." + file_type command.params["size"] = str(size) @@ -141,6 +144,7 @@ def delete_image(file_name): command = proto.PbCommand() command.operation = proto.PbOperation.DELETE_IMAGE command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] command.params["file"] = file_name @@ -159,6 +163,7 @@ def rename_image(file_name, new_file_name): command = proto.PbCommand() command.operation = proto.PbOperation.RENAME_IMAGE command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] command.params["from"] = file_name command.params["to"] = new_file_name @@ -176,8 +181,14 @@ def delete_file(file_path): """ if os.path.exists(file_path): os.remove(file_path) - return {"status": True, "msg": f"File deleted: {file_path}"} - return {"status": False, "msg": f"File to delete not found: {file_path}"} + return { + "status": True, + "msg": _(u"File deleted: %(file_path)s", file_path=file_path), + } + return { + "status": False, + "msg": _(u"File to delete not found: %(file_path)s", file_path=file_path), + } def rename_file(file_path, target_path): @@ -187,8 +198,14 @@ def rename_file(file_path, target_path): """ if os.path.exists(PurePath(target_path).parent): os.rename(file_path, target_path) - return {"status": True, "msg": f"File moved to: {target_path}"} - return {"status": False, "msg": f"Unable to move to: {target_path}"} + return { + "status": True, + "msg": _(u"File moved to: %(target_path)s", target_path=target_path), + } + return { + "status": False, + "msg": _(u"Unable to move file to: %(target_path)s", target_path=target_path), + } def unzip_file(file_name, member=False, members=False): @@ -303,7 +320,7 @@ def download_file_to_iso(url, *iso_args): return { "status": True, - "msg": f"Created CD-ROM ISO image with arguments \"" + " ".join(iso_args) + "\"", + "msg": _(u"Created CD-ROM ISO image with arguments \"%(value)s\"", value=" ".join(iso_args)), "file_name": iso_filename, } @@ -331,7 +348,14 @@ def download_to_dir(url, save_dir): logging.info("Response content-type: %s", req.headers["content-type"]) logging.info("Response status code: %s", req.status_code) - return {"status": True, "msg": f"{file_name} downloaded to {save_dir}"} + return { + "status": True, + "msg": _( + u"%(file_name)s downloaded to %(save_dir)s", + file_name=file_name, + save_dir=save_dir, + ), + } def write_config(file_name): @@ -369,7 +393,7 @@ def write_config(file_name): json_file, indent=4 ) - return {"status": True, "msg": f"Saved config to {file_name}"} + return {"status": True, "msg": _(u"Saved configuration file to %(file_name)s", file_name=file_name)} except (IOError, ValueError, EOFError, TypeError) as error: logging.error(str(error)) delete_file(file_name) @@ -377,7 +401,10 @@ def write_config(file_name): except: logging.error("Could not write to file: %s", file_name) delete_file(file_name) - return {"status": False, "msg": f"Could not write to file: {file_name}"} + return { + "status": False, + "msg": _(u"Could not write to file: %(file_name)s", file_name=file_name), + } def read_config(file_name): @@ -434,14 +461,20 @@ def read_config(file_name): kwargs[param] = params[param] attach_image(row["id"], **kwargs) else: - return {"status": False, "msg": "Invalid config file format."} - return {"status": True, "msg": f"Loaded config from: {file_name}"} + return {"status": False, "msg": _(u"Invalid configuration file format")} + return { + "status": True, + "msg": _(u"Loaded configurations from: %(file_name)s", file_name=file_name), + } except (IOError, ValueError, EOFError, TypeError) as error: logging.error(str(error)) return {"status": False, "msg": str(error)} except: logging.error("Could not read file: %s", file_name) - return {"status": False, "msg": f"Could not read file: {file_name}"} + return { + "status": False, + "msg": _(u"Could not read configuration file: %(file_name)s", file_name=file_name), + } def write_drive_properties(file_name, conf): @@ -455,7 +488,10 @@ def write_drive_properties(file_name, conf): try: with open(file_path, "w") as json_file: dump(conf, json_file, indent=4) - return {"status": True, "msg": f"Created file: {file_path}"} + return { + "status": True, + "msg": _(u"Created properties file: %(file_path)s", file_path=file_path), + } except (IOError, ValueError, EOFError, TypeError) as error: logging.error(str(error)) delete_file(file_path) @@ -463,23 +499,33 @@ def write_drive_properties(file_name, conf): except: logging.error("Could not write to file: %s", file_path) delete_file(file_path) - return {"status": False, "msg": f"Could not write to file: {file_path}"} + return { + "status": False, + "msg": _(u"Could not write to properties file: %(file_path)s", file_path=file_path), + } -def read_drive_properties(path_name): +def read_drive_properties(file_path): """ Reads drive properties from json formatted file. - Takes (str) path_name as argument. + Takes (str) file_path as argument. Returns (dict) with (bool) status, (str) msg, (dict) conf """ from json import load try: - with open(path_name) as json_file: + with open(file_path) as json_file: conf = load(json_file) - return {"status": True, "msg": f"Read from file: {path_name}", "conf": conf} + return { + "status": True, + "msg": _(u"Read properties from file: %(file_path)s", file_path=file_path), + "conf": conf, + } except (IOError, ValueError, EOFError, TypeError) as error: logging.error(str(error)) return {"status": False, "msg": str(error)} except: - logging.error("Could not read file: %s", path_name) - return {"status": False, "msg": f"Could not read file: {path_name}"} + logging.error("Could not read file: %s", file_path) + return { + "status": False, + "msg": _(u"Could not read properties from file: %(file_path)s", file_path=file_path), + } diff --git a/src/web/pi_cmds.py b/src/web/pi_cmds.py index 5e44e8a5..da9718e2 100644 --- a/src/web/pi_cmds.py +++ b/src/web/pi_cmds.py @@ -5,6 +5,7 @@ Module for methods controlling and getting information about the Pi's Linux syst import subprocess import asyncio import logging +from flask_babel import _ from settings import AUTH_GROUP @@ -175,6 +176,6 @@ def auth_active(): if AUTH_GROUP in groups: return { "status": True, - "msg": "You must log in to use this function!", + "msg": _(u"You must log in to use this function"), } return {"status": False, "msg": ""} diff --git a/src/web/ractl_cmds.py b/src/web/ractl_cmds.py index 0379d9a7..6ed3b060 100644 --- a/src/web/ractl_cmds.py +++ b/src/web/ractl_cmds.py @@ -5,6 +5,7 @@ Module for commands sent to the RaSCSI backend service. from settings import REMOVABLE_DEVICE_TYPES from socket_cmds import send_pb_command from flask import current_app +from flask_babel import _ import rascsi_interface_pb2 as proto @@ -24,6 +25,7 @@ def get_server_info(): command = proto.PbCommand() command.operation = proto.PbOperation.SERVER_INFO command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] data = send_pb_command(command.SerializeToString()) result = proto.PbResult() @@ -82,6 +84,7 @@ def get_reserved_ids(): command = proto.PbCommand() command.operation = proto.PbOperation.RESERVED_IDS_INFO command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] data = send_pb_command(command.SerializeToString()) result = proto.PbResult() @@ -103,6 +106,7 @@ def get_network_info(): command = proto.PbCommand() command.operation = proto.PbOperation.NETWORK_INTERFACES_INFO command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] data = send_pb_command(command.SerializeToString()) result = proto.PbResult() @@ -121,6 +125,7 @@ def get_device_types(): command = proto.PbCommand() command.operation = proto.PbOperation.DEVICE_TYPES_INFO command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] data = send_pb_command(command.SerializeToString()) result = proto.PbResult() @@ -142,6 +147,8 @@ def get_image_files_info(): """ command = proto.PbCommand() command.operation = proto.PbOperation.DEFAULT_IMAGE_FILES_INFO + command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] data = send_pb_command(command.SerializeToString()) result = proto.PbResult() @@ -170,6 +177,7 @@ def attach_image(scsi_id, **kwargs): """ command = proto.PbCommand() command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] devices = proto.PbDeviceDefinition() devices.id = int(scsi_id) @@ -195,8 +203,12 @@ def attach_image(scsi_id, **kwargs): if current_type != device_type: return { "status": False, - "msg": "Cannot insert an image for " + device_type + \ - " into a " + current_type + " device." + "msg": _( + u"Cannot insert an image for %(device_type)s into a " + u"%(current_device_type)s device", + device_type=device_type, + current_device_type=current_type + ), } command.operation = proto.PbOperation.INSERT # Handling attaching a new device @@ -241,6 +253,7 @@ def detach_by_id(scsi_id, unit=None): command.operation = proto.PbOperation.DETACH command.devices.append(devices) command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] data = send_pb_command(command.SerializeToString()) result = proto.PbResult() @@ -256,6 +269,7 @@ def detach_all(): command = proto.PbCommand() command.operation = proto.PbOperation.DETACH_ALL command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] data = send_pb_command(command.SerializeToString()) result = proto.PbResult() @@ -278,6 +292,7 @@ def eject_by_id(scsi_id, unit=None): command.operation = proto.PbOperation.EJECT command.devices.append(devices) command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] data = send_pb_command(command.SerializeToString()) result = proto.PbResult() @@ -297,6 +312,7 @@ def list_devices(scsi_id=None, unit=None): command = proto.PbCommand() command.operation = proto.PbOperation.DEVICES_INFO command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] # If method is called with scsi_id parameter, return the info on those devices # Otherwise, return the info on all attached devices @@ -374,6 +390,7 @@ def reserve_scsi_ids(reserved_scsi_ids): command.operation = proto.PbOperation.RESERVE_IDS command.params["ids"] = ",".join(reserved_scsi_ids) command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] data = send_pb_command(command.SerializeToString()) result = proto.PbResult() @@ -391,6 +408,7 @@ def set_log_level(log_level): command.operation = proto.PbOperation.LOG_LEVEL command.params["level"] = str(log_level) command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] data = send_pb_command(command.SerializeToString()) result = proto.PbResult() @@ -408,6 +426,7 @@ def shutdown_pi(mode): command.operation = proto.PbOperation.SHUT_DOWN command.params["mode"] = str(mode) command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] data = send_pb_command(command.SerializeToString()) result = proto.PbResult() @@ -424,6 +443,7 @@ def is_token_auth(): command = proto.PbCommand() command.operation = proto.PbOperation.CHECK_AUTHENTICATION command.params["token"] = current_app.config["TOKEN"] + command.params["locale"] = current_app.config["LOCALE"] data = send_pb_command(command.SerializeToString()) result = proto.PbResult() diff --git a/src/web/requirements.txt b/src/web/requirements.txt index 9ee82b36..c9ced2c3 100644 --- a/src/web/requirements.txt +++ b/src/web/requirements.txt @@ -7,3 +7,4 @@ MarkupSafe==2.0.1 protobuf==3.17.3 requests==2.26.0 simplepam==0.1.5 +flask_babel==2.0.0 diff --git a/src/web/socket_cmds.py b/src/web/socket_cmds.py index ba540e76..710f1394 100644 --- a/src/web/socket_cmds.py +++ b/src/web/socket_cmds.py @@ -4,6 +4,7 @@ Module for sending and receiving data over a socket connection with the RaSCSI b import logging from flask import abort +from flask_babel import _ from time import sleep def send_pb_command(payload): @@ -35,9 +36,12 @@ def send_pb_command(payload): logging.error(error_msg) # After failing all attempts, throw a 404 error - abort(404, "The RaSCSI Web Interface failed to connect to RaSCSI at " + str(host) + \ - ":" + str(port) + " with error: " + error_msg + \ - ". The RaSCSI service is not running or may have crashed.") + abort(404, _( + u"The RaSCSI Web Interface failed to connect to RaSCSI at %(host)s:%(port)s " + u"with error: %(error_msg)s. The RaSCSI process is not running or may have crashed.", + host=host, port=port, error_msg=error_msg, + ) + ) def send_over_socket(sock, payload): @@ -72,9 +76,11 @@ def send_over_socket(sock, payload): "RaSCSI may have crashed." ) abort( - 503, "The RaSCSI Web Interface lost connection to RaSCSI. " - "Please go back and try again. " - "If the issue persists, please report a bug." + 503, _( + u"The RaSCSI Web Interface lost connection to RaSCSI. " + u"Please go back and try again. " + u"If the issue persists, please report a bug." + ) ) chunks.append(chunk) bytes_recvd = bytes_recvd + len(chunk) @@ -86,8 +92,9 @@ def send_over_socket(sock, payload): "RaSCSI may have crashed." ) abort( - 500, - "The RaSCSI Web Interface did not get a valid response from RaSCSI. " - "Please go back and try again. " - "If the issue persists, please report a bug." + 500, _( + u"The RaSCSI Web Interface did not get a valid response from RaSCSI. " + u"Please go back and try again. " + u"If the issue persists, please report a bug." + ) ) diff --git a/src/web/start.sh b/src/web/start.sh index 3cba5077..4a6e3d36 100755 --- a/src/web/start.sh +++ b/src/web/start.sh @@ -74,6 +74,8 @@ else fi set -e +pybabel compile -d translations + # parse arguments while [ "$1" != "" ]; do PARAM=$(echo "$1" | awk -F= '{print $1}') diff --git a/src/web/templates/base.html b/src/web/templates/base.html index 0a6c2765..5465227a 100644 --- a/src/web/templates/base.html +++ b/src/web/templates/base.html @@ -26,12 +26,12 @@ @@ -45,19 +45,19 @@
{% if auth_active %} {% if username %} - Logged in as {{ username }}Log Out + {{ _("Logged in as %(username)s", username=username) }} – {{ _("Log Out") }} {% else %}
-
Log In to Use Web Interface
- - +
{{ _("Log In to Use Web Interface") }}
+ +
{% endif %} {% else %} - Web Interface Authentication Disabled – See Wiki for more information + {{ _("Web Interface Authentication Disabled") }} – {{ _("See Wiki for more information", url="https://github.com/akuker/RASCSI/wiki/Web-Interface#enable-authentication") }} {% endif %} @@ -84,8 +84,8 @@ {% block content %}{% endblock content %} diff --git a/src/web/templates/drives.html b/src/web/templates/drives.html index 3cd8bf6f..24656035 100644 --- a/src/web/templates/drives.html +++ b/src/web/templates/drives.html @@ -1,19 +1,19 @@ {% extends "base.html" %} {% block content %} -

Cancel

-

Disclaimer

-

These device profiles are provided as-is with no guarantee to work on the systems mentioned. You may need appropirate device drivers and/or configuration parameters. If you have improvement suggestions or success stories to share we would love to hear from you, so please connect with us at GitHub or Discord!

-

Hard Drives

+

{{ _("Cancel") }}

+

{{ _("Disclaimer") }}

+

{{ _("These device profiles are provided as-is with no guarantee to work equally to the actual physical device they are named after. You may need to provide appropirate device drivers and/or configuration parameters for them to function properly. If you would like to see data modified, or have additional devices to add to the list, please raise an issue ticket at GitHub.", url="https://github.com/akuker/RASCSI/issues") }}

+

{{ _("Hard Drives") }}

- - - - - + + + + + {% for hd in hd_conf %} @@ -22,7 +22,7 @@ @@ -48,16 +48,16 @@
-

CD-ROM Drives

-

This will create a properties file for the given CD-ROM image. No new image file will be created.

+

{{ _("CD-ROM Drives") }}

+

{{ _("This will create a properties file for the given CD-ROM image. No new image file will be created.") }}

NameSize (MB)DescriptionRef.Action{{ _("Name") }}{{ _("Size (MB)") }}{{ _("Description") }}{{ _("Ref.") }}{{ _("Action") }}
{{ hd.description }} {% if hd.url != "" %} - Link + {{ _("Link") }} {% else %} - {% endif %} @@ -36,9 +36,9 @@ - + .{{ hd.file_type }} - +
- - - - - + + + + + {% for cd in cd_conf %} @@ -66,7 +66,7 @@ @@ -95,15 +95,15 @@
-

Removable Drives

+

{{ _("Removable Drives") }}

NameSize (MB)DescriptionRef.Action{{ _("Name") }}{{ _("Size (MB)") }}{{ _("Description") }}{{ _("Ref.") }}{{ _("Action") }}
{{ cd.description }} {% if cd.url != "" %} - Link + {{ _("Link") }} {% else %} - {% endif %} @@ -77,7 +77,7 @@ - + - +
- - - - - + + + + + {% for rm in rm_conf %} @@ -112,7 +112,7 @@ {% endfor %}
NameSize (MB)DescriptionRef.Action{{ _("Name") }}{{ _("Size (MB)") }}{{ _("Description") }}{{ _("Ref.") }}{{ _("Action") }}
{{ rm.description }} {% if rm.url != "" %} - Link + {{ _("Link") }} {% else %} - {% endif %} @@ -126,16 +126,16 @@ - + .{{ rm.file_type }} - +
-

Available disk space on the Pi: {{ free_disk }} MB

-

Cancel

+

{{ _("%(disk_space)s MB disk space remaining on the Pi", disk_space=free_disk) }}

+

{{ _("Cancel") }}

{% endblock content %} diff --git a/src/web/templates/index.html b/src/web/templates/index.html index 34032fc4..ef323959 100644 --- a/src/web/templates/index.html +++ b/src/web/templates/index.html @@ -3,12 +3,12 @@
- Current RaSCSI Configuration + {{ _("Current RaSCSI Configuration") }}
    -
  • Displays the currently attached devices for each available SCSI ID.
  • -
  • Save and load device configurations into {{ CFG_DIR }}
  • -
  • The default configuration will be loaded when the Web UI starts up, if available.
  • +
  • {{ _("Displays the currently attached devices for each available SCSI ID.") }}
  • +
  • {{ _("Save and load device configurations, stored as json files in %(config_dir)s", config_dir=CFG_DIR) }}
  • +
  • {{ _("To have a particular device configuration load when RaSCSI starts, save it as default.") }}
@@ -22,31 +22,31 @@ {% endfor %} {% else %} {% endif %} - - + +

- +

- + {% if units %} - + {% endif %} - - - - - + + + + + {% for device in devices %} @@ -81,7 +81,7 @@ {% endif %} {% endfor %} - + {% else %} {{ device.file }} @@ -95,28 +95,28 @@ @@ -126,13 +126,13 @@ {% endif %} - + {% endif %} @@ -141,31 +141,31 @@
ID{{ _("ID") }}LUN{{ _("LUN") }}TypeStatusFileProductActions{{ _("Type") }}{{ _("Status") }}{{ _("File") }}{{ _("Product") }}{{ _("Actions") }}
{% if device.device_type != "-" %} {% if device.device_type in REMOVABLE_DEVICE_TYPES and "No Media" not in device.status %} -
+ - +
{% else %} -
+ - +
{% endif %}
- +
{% else %} -
+ - +
{% endif %}
Reserved ID{{ _("Reserved ID") }} {{ RESERVATIONS[device.id] }}
- +
-

- +

+


- Image File Management + {{ _("Image File Management") }}
    -
  • Manage image files in the active RaSCSI image directory: {{ base_dir }} with a scan depth of {{ scan_depth }}.
  • -
  • Select a valid SCSI ID and LUN to attach to. Unless you know what you're doing, always use LUN 0. +
  • {{ _("Manage image files in the active RaSCSI image directory: %(directory)s with a scan depth of %(scan_depth)s.", directory=base_dir, scan_depth=scan_depth) }}
  • +
  • {{ _("Select a valid SCSI ID and LUN to attach to. Unless you know what you're doing, always use LUN 0.", url="https://en.wikipedia.org/wiki/Logical_unit_number") }}
  • -
  • If RaSCSI was unable to detect the device type associated with the image, you can choose the type from the dropdown.
  • -
  • Types: SAHD = SASI HDD | SCHD = SCSI HDD | SCRM = Removable | SCMO = Magneto-Optical | SCCD = CD-ROM | SCBR = Host Bridge | SCDP = DaynaPORT
  • +
  • {{ _("If RaSCSI was unable to detect the device type associated with the image, you can choose the type from the dropdown.") }}
  • +
  • {{ _("Types: SAHD = SASI HDD | SCHD = SCSI HDD | SCRM = Removable | SCMO = Magneto-Optical | SCCD = CD-ROM | SCBR = Host Bridge | SCDP = DaynaPORT") }}
- - - + + + {% for file in files %} @@ -181,7 +181,7 @@ {% endfor %} - + @@ -201,7 +201,7 @@ - + @@ -292,22 +292,22 @@ {% endfor %}
FileSizeActions{{ _("File") }}{{ _("Size") }}{{ _("Actions") }}
- +
{% if file["name"] in attached_images %}
- Attached! + {{ _("Attached!") }}
{% else %} {% if file["name"].lower().endswith(ARCHIVE_FILE_SUFFIX) %}
- +
{% else %}
- + - + {% if file["detected_type"] != "UNDEFINED" %} @@ -265,7 +265,7 @@ {% else %} - + {% endif %}
-
+ - +
-
+ - +
{% endif %}
-

Available disk space on the Pi: {{ free_disk }} MB

+

{{ _("%(disk_space)s MB disk space remaining on the Pi", disk_space=free_disk) }}


- Attach Ethernet Adapter + {{ _("Attach Ethernet Adapter") }}
    -
  • Emulates a SCSI DaynaPORT Ethernet Adapter. Host drivers and configuration required. +
  • {{ _("Emulates a SCSI DaynaPORT Ethernet Adapter. Host drivers and configuration required.", url="https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link") }}
  • -
  • If you have a DHCP setup, choose only the interface, and ignore the Static IP fields when attaching.
  • -
  • Configure network forwarding by running easyinstall.sh, or follow the manual steps in the wiki. +
  • {{ _("If you have a DHCP setup, choose only the interface you have configured the bridge with. You can ignore the Static IP fields when attaching.") }}
  • +
  • {{ _("Configure the network bridge by running easyinstall.sh, or follow the manual steps in the wiki.", url="https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link#manual-setup") }}
  • {% if bridge_configured %}
  • -
  • The rascsi_bridge interface is active and ready to be used by DaynaPORT!
  • +
  • {{ _("The rascsi_bridge interface is active and ready to be used by DaynaPORT!") }}
  • {% endif %}
@@ -315,7 +315,7 @@
- + - + - + - +
{% if macproxy_configured %} -

Macproxy is running at {{ ip_addr }} port 5000

+

{{ _("Macproxy is running at %(ip_addr)s (default port 5000)", ip_addr=ip_addr) }}

{% else %} -

Install Macproxy to browse the Web with any vintage browser. It's not just for Macs!

+

{{ _("Install Macproxy to browse the Web with any vintage browser. It's not just for Macs!", url="https://github.com/akuker/RASCSI/wiki/Vintage-Web-Proxy#macproxy") }}

{% endif %}
- Upload File + {{ _("Upload File") }}
    -
  • Uploads file to {{ base_dir }}. The largest file size accepted is {{ max_file_size }} MB.
  • -
  • For unrecognized file types, try renaming hard drive images to '.hds' and CD-ROM images to '.iso' before uploading.
  • -
  • Recognized file types: {{ valid_file_suffix }}
  • +
  • {{ _("Uploads file to %(directory)s. The largest file size accepted is %(max_file_size)s MB.", directory=base_dir, max_file_size=max_file_size) }}
  • +
  • {{ _("For unrecognized file types, try renaming hard drive images to '.hds', CD-ROM images to '.iso', and removable drive images to '.hdr' before uploading.") }}
  • +
  • {{ _("Recognized file types: %(valid_file_suffix)s", valid_file_suffix=valid_file_suffix) }}
@@ -380,10 +380,10 @@
- Download File to Images + {{ _("Download File to Images") }}
    -
  • Given a URL, download that file to the {{ base_dir }} directory.
  • +
  • {{ _("Given a URL, download that file to the %(directory)s directory.", directory=base_dir) }}
@@ -391,9 +391,9 @@
- - - + + +
@@ -403,13 +403,12 @@
- Download File to AppleShare + {{ _("Download File to AppleShare") }}
    -
  • Given a URL, download that file to the {{ AFP_DIR }} directory and share it over AFP.
  • -
  • Manage the files you download here through AppleShare on your vintage Mac.
  • -
  • Requires Netatalk to be installed and configured correctly for your network. -
  • +
  • {{ _("Given a URL, download that file to the %(directory)s directory and share it over AFP.", directory=AFP_DIR) }}
  • +
  • {{ _("Manage the files you download here through AppleShare on your vintage Mac.") }}
  • +
  • {{ _("Requires Netatalk to be installed and configured correctly for your network.", url="https://github.com/akuker/RASCSI/wiki/AFP-File-Sharing") }}
@@ -418,43 +417,42 @@
- - - + + +
{% if netatalk_configured == 1 %} -

The AppleShare server is running. No active connections

+

{{ _("The AppleShare server is running. No active connections.") }}

{% elif netatalk_configured == 2 %} -

{{ netatalk_configured - 1 }} active AFP connection

+

{{ _("%(value)d active AFP connection", value=(netatalk_configured - 1)) }}

{% elif netatalk_configured > 2 %} -

{{ netatalk_configured - 1 }} active AFP connections

+

{{ _("%(value)d active AFP connections", value=(netatalk_configured - 1)) }}

{% endif %} {% else %} -

Install Netatalk to use the AppleShare File Server.

+

{{ _("Install Netatalk to use the AppleShare File Server.", url="https://github.com/akuker/RASCSI/wiki/AFP-File-Sharing") }}

{% endif %}
- Download File and Create CD-ROM ISO image + {{ _("Download File and Create CD-ROM image") }}
    -
  • Given a URL this will download a file, create a CD-ROM image with the selected file system, and mount it on the SCSI ID given.
  • -
  • HFS is for Mac OS, Joliet for Windows, and Rock Ridge for POSIX.
  • -
  • On Mac OS, requires a compatible CD-ROM driver installed on the target system. -
  • -
  • If the target file is a zip archive, we will attempt to unzip it and store the resulting files only.
  • +
  • {{ _("Create an ISO file system CD-ROM image with the downloaded file, and mount it on the given SCSI ID.") }}
  • +
  • {{ _("HFS is for Mac OS, Joliet for Windows, and Rock Ridge for POSIX.") }}
  • +
  • {{ _("On Mac OS, a compatible CD-ROM driver is required.", url="https://github.com/akuker/RASCSI/wiki/Drive-Setup#Mounting_CD_ISO_or_MO_images") }}
  • +
  • {{ _("If the downloaded file is a zip archive, we will attempt to unzip it and store the resulting files.") }}
@@ -496,41 +494,41 @@
- Create Empty Disk Image File + {{ _("Create Empty Disk Image File") }}
    -
  • The Generic image type is recommended for most systems
  • -
  • APPLE GENUINE and NEC GENUINE image types will make RaSCSI masquerade as a particular drive type that are recognized by Mac and PC98 systems, respectively.
  • -
  • SASI images should only be used on early X68000 or UNIX workstation systems that use this pre-SCSI standard.
  • +
  • {{ _("The Generic image type is recommended for most computer platforms.") }}
  • +
  • {{ _("APPLE GENUINE (.hda) and NEC GENUINE (.hdn) image types will make RaSCSI behave as a particular drive type that are recognized by Mac and PC98 systems, respectively.") }}
  • +
  • {{ _("SASI images should only be used on the original Sharp X68000, or other legacy systems that utilize this pre-SCSI standard.") }}
- +
- - - + + + - +
@@ -540,32 +538,32 @@
- Create Named Drive + {{ _("Create Named Drive") }}
    -
  • Here you can create pairs of images and properties files from a list of real-life drives.
  • -
  • This will make RaSCSI use certain vendor strings and block sizes that may improve compatibility with certain systems
  • +
  • {{ _("Create pairs of images and properties files from a list of real-life drives.") }}
  • +
  • {{ _("This will make RaSCSI use certain vendor strings and block sizes that may improve compatibility with certain systems.") }}
-

Create a named disk image that mimics real-life drives

+

{{ _("Create a named disk image that mimics real-life drives") }}


- Logging + {{ _("Logging") }}
    -
  • Get a certain number of lines of service logs with the given scope.
  • +
  • {{ _("Fetch a certain number of lines of system logs with the given scope.") }}
- - - + + + - - - + + +
@@ -587,18 +585,18 @@
- Server Log Level + {{ _("Server Log Level") }}
    -
  • Change the log level of the RaSCSI backend service.
  • -
  • The dropdown will indicate the current log level.
  • +
  • {{ _("Change the log level of the RaSCSI backend process.") }}
  • +
  • {{ _("The current dropdown selection indicates the active log level.") }}
- + - + - +
@@ -616,23 +614,23 @@
- Raspberry Pi Operations + {{ _("Raspberry Pi Operations") }}
    -
  • Reboot or shut down the Raspberry Pi that RaSCSI is running on.
  • -
  • IMPORTANT: Always shut down the Pi before turning off the power. Failing to do so may lead to data corruption.
  • +
  • {{ _("Reboot or shut down the Raspberry Pi that RaSCSI is running on.") }}
  • +
  • {{ _("IMPORTANT: Always shut down the Pi before turning off the power. Failing to do so may lead to data loss.") }}
- + - +
diff --git a/src/web/translations/sv/LC_MESSAGES/messages.po b/src/web/translations/sv/LC_MESSAGES/messages.po new file mode 100644 index 00000000..a3c31a8c --- /dev/null +++ b/src/web/translations/sv/LC_MESSAGES/messages.po @@ -0,0 +1,1127 @@ +# Swedish translations for RaSCSI. +# Copyright (C) 2021 akuker +# This file is distributed under the same license as the RaSCSI project. +# Daniel Markstedt , 2021. +# +msgid "" +msgstr "" +"Project-Id-Version: RaSCSI 68kmla Edition\n" +"Report-Msgid-Bugs-To: https://github.com/akuker/RASCSI/issues\n" +"POT-Creation-Date: 2021-12-25 23:33-0800\n" +"PO-Revision-Date: 2021-12-24 16:16-0800\n" +"Last-Translator: Daniel Markstedt \n" +"Language: sv\n" +"Language-Team: N/A\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.9.1\n" + +#: file_cmds.py:186 +#, python-format +msgid "File deleted: %(file_path)s" +msgstr "Raderade filen %(file_path)s" + +#: file_cmds.py:190 +#, python-format +msgid "File to delete not found: %(file_path)s" +msgstr "Kunde ej radera filen %(file_path)s" + +#: file_cmds.py:203 +#, python-format +msgid "File moved to: %(target_path)s" +msgstr "Flyttade filen till %(target_path)s" + +#: file_cmds.py:207 +#, python-format +msgid "Unable to move file to: %(target_path)s" +msgstr "Kunde ej flytta filen till %(target_path)s" + +#: file_cmds.py:323 +#, python-format +msgid "Created CD-ROM ISO image with arguments \"%(value)s\"" +msgstr "Skapade en CD-ROM ISO-fil med argumenten \"%(value)s\"" + +#: file_cmds.py:353 +#, python-format +msgid "%(file_name)s downloaded to %(save_dir)s" +msgstr "Laddade ner %(file_name)s till %(save_dir)s" + +#: file_cmds.py:396 +#, python-format +msgid "Saved configuration file to %(file_name)s" +msgstr "Sparade konfigurationsfilen som %(file_name)s" + +#: file_cmds.py:406 +#, python-format +msgid "Could not write to file: %(file_name)s" +msgstr "Kunde ej skriva till filen %(file_name)s" + +#: file_cmds.py:464 +msgid "Invalid configuration file format" +msgstr "Ogiltigt konfigurationsfilformat" + +#: file_cmds.py:467 +#, python-format +msgid "Loaded configurations from: %(file_name)s" +msgstr "Laddade konfigurationer från %(file_name)s" + +#: file_cmds.py:476 +#, python-format +msgid "Could not read configuration file: %(file_name)s" +msgstr "Kunde ej läsa konfigurationer från filen %(file_name)s" + +#: file_cmds.py:493 +#, python-format +msgid "Created properties file: %(file_path)s" +msgstr "Skapade egenskapsfilen %(file_path)s" + +#: file_cmds.py:504 +#, python-format +msgid "Could not write to properties file: %(file_path)s" +msgstr "Kunde ej spara egenskaper till filen %(file_path)s" + +#: file_cmds.py:520 +#, python-format +msgid "Read properties from file: %(file_path)s" +msgstr "Läste egenskaper från filen %(file_path)s" + +#: file_cmds.py:530 +#, python-format +msgid "Could not read properties from file: %(file_path)s" +msgstr "Kunde ej läsa egenskaper från filen %(file_path)s" + +#: pi_cmds.py:179 +msgid "You must log in to use this function" +msgstr "Du måste logga in för att använda den här funktionen" + +#: ractl_cmds.py:206 +#, python-format +msgid "" +"Cannot insert an image for %(device_type)s into a %(current_device_type)s" +" device" +msgstr "" +"Det går inte att mata in en skiva av typ %(device_type)s i en enhet av " +"typ %(current_device_type)s" + +#: socket_cmds.py:39 +#, python-format +msgid "" +"The RaSCSI Web Interface failed to connect to RaSCSI at %(host)s:%(port)s" +" with error: %(error_msg)s. The RaSCSI process is not running or may have" +" crashed." +msgstr "" +"RaSCSIs webbgränssnitt kunde inte ansluta till RaSCSI på " +"%(host)s:%(port)s med felmeddelande %(error_msg)s. RaSCSI-processen är " +"antingen avslagen eller har krashat." + +#: socket_cmds.py:79 +msgid "" +"The RaSCSI Web Interface lost connection to RaSCSI. Please go back and " +"try again. If the issue persists, please report a bug." +msgstr "" +"RaSCSIs webbgränssnitt tappade kontaken med RaSCSI. Gå tillbaks och " +"försök igen. Om samma fel upprepas så rapportera en bugg." + +#: socket_cmds.py:95 +msgid "" +"The RaSCSI Web Interface did not get a valid response from RaSCSI. Please" +" go back and try again. If the issue persists, please report a bug." +msgstr "" +"RaSCSIs webbgränssnitt fick en ogiltig respons från RaSCSI. Gå tillbaks " +"och försök igen. Om samma fel upprepas så rapportera en bugg." + +#: web.py:97 +msgid "" +"RaSCSI is password protected. Start the Web Interface with the --password" +" parameter." +msgstr "" +"RaSCSI är lösenordsskyddat. Start webbgränssnittet med parametern " +"--password ." + +#: web.py:191 +#, python-format +msgid "Could not read drive properties from %(properties_file)s" +msgstr "Kunde ej läsa egenskaper från %(properties_file)s" + +#: web.py:255 +#, python-format +msgid "You must log in with credentials for a user in the '%(group)s' group" +msgstr "Du måste logga in som en användare som tillhör %(group)s-gruppen" + +#: web.py:308 web.py:843 +#, python-format +msgid "Image file created: %(file_name)s" +msgstr "Skapade skivbildfil: %(file_name)s" + +#: web.py:433 +msgid "An error occurred when fetching logs." +msgstr "Ett fel inträffade när vi skaffade loggar." + +#: web.py:448 +#, python-format +msgid "Log level set to %(value)s" +msgstr "Ställde in loggnivån till %(value)s" + +#: web.py:467 +#, python-format +msgid "Please follow the instructions at %(url)s" +msgstr "Följ instruktionerna på %(url)s" + +#: web.py:471 +msgid "Configure IPv4 forwarding before using a wireless network device." +msgstr "" +"Ställ in IPv4-vidarebefodring innan du använder en trådlös " +"nätverksadapter." + +#: web.py:475 +msgid "Configure NAT before using a wireless network device." +msgstr "Ställ in NAT innan du använder en trådlös nätverksadapter." + +#: web.py:480 web.py:484 +msgid "Configure the network bridge before using a wired network device." +msgstr "Ställ in nätverksbryggan innan du använder ett nätverksgränssnitt." + +#: web.py:497 +#, python-format +msgid "Attached DaynaPORT to SCSI ID %(id_number)s" +msgstr "Anslöt DaynaPORT till SCSI-id %(id_number)s" + +#: web.py:545 +#, python-format +msgid "Attached %(file_name)s to SCSI ID %(id_number)s LUN %(unit_number)s" +msgstr "Anslöt %(file_name)s till SCSI-id %(id_number)s LUN %(unit_number)s" + +#: web.py:548 +#, python-format +msgid "" +"The image file size %(file_size)s bytes is not a multiple of " +"%(block_size)s. RaSCSI will ignore the trailing data. The image may be " +"corrupted, so proceed with caution." +msgstr "" +"Filstorleken %(file_size)s bytes är inte en multipel av %(block_size)s. " +"RaSCSI ignorerar den överflödiga datan. Skivbilden är möjligen förstörd, " +"så var försiktig när du använder den." + +#: web.py:554 +#, python-format +msgid "" +"Failed to attach %(file_name)s to SCSI ID %(id_number)s LUN " +"%(unit_number)s" +msgstr "" +"Kunde inte ansluta %(file_name)s till SCSI-id %(id_number)s LUN " +"%(unit_number)s" + +#: web.py:568 +msgid "Detached all SCSI devices" +msgstr "Kopplade ifrån alla SCSI-enheter" + +#: web.py:585 +#, python-format +msgid "Detached SCSI ID %(id_number)s LUN %(unit_number)s" +msgstr "Kopplade ifrån SCSI-id %(id_number)s LUN %(unit_number)s" + +#: web.py:589 +#, python-format +msgid "" +"Failed to detach %(file_name)s from SCSI ID %(id_number)s LUN " +"%(unit_number)s" +msgstr "" +"Kunde ej koppla ifrån %(file_name)s från SCSI-id %(id_number)s LUN " +"%(unit_number)s" + +#: web.py:606 +#, python-format +msgid "Ejected SCSI ID %(id_number)s LUN %(unit_number)s" +msgstr "Utmatade SCSI-id %(id_number)s LUN %(unit_number)s" + +#: web.py:610 +#, python-format +msgid "" +"Failed to eject %(file_name)s from SCSI ID %(id_number)s LUN " +"%(unit_number)s" +msgstr "" +"Kunde ej utmata %(file_name)s från SCSI-id %(id_number)s LUN " +"%(unit_number)s" + +#: web.py:633 +msgid "DEVICE INFO" +msgstr "ENHETSDATA" + +#: web.py:635 +#, python-format +msgid "SCSI ID: %(id_number)s" +msgstr "SCSI-id: %(id_number)s" + +#: web.py:636 +#, python-format +msgid "LUN: %(unit_number)s" +msgstr "LUN: %(unit_number)s" + +#: web.py:637 +#, python-format +msgid "Type: %(device_type)s" +msgstr "Typ: %(device_type)s" + +#: web.py:638 +#, python-format +msgid "Status: %(device_status)s" +msgstr "Status: %(device_status)s" + +#: web.py:639 +#, python-format +msgid "File: %(image_file)s" +msgstr "Fil: %(image_file)s" + +#: web.py:640 +#, python-format +msgid "Parameters: %(value)s" +msgstr "Parametrar: %(value)s" + +#: web.py:641 +#, python-format +msgid "Vendor: %(value)s" +msgstr "Tillverkare: %(value)s" + +#: web.py:642 +#, python-format +msgid "Product: %(value)s" +msgstr "Produkt: %(value)s" + +#: web.py:643 +#, python-format +msgid "Revision: %(revision_number)s" +msgstr "Revision: %(revision_number)s" + +#: web.py:644 +#, python-format +msgid "Block Size: %(value)s bytes" +msgstr "Blockstorlek: %(value)s bytes" + +#: web.py:645 +#, python-format +msgid "Image Size: %(value)s bytes" +msgstr "Skivbildsstorlek: %(value)s bytes" + +#: web.py:664 +#, python-format +msgid "Reserved SCSI ID %(id_number)s" +msgstr "Reserverat SCSI-id %(id_number)s" + +#: web.py:667 +#, python-format +msgid "Failed to reserve SCSI ID %(id_number)s" +msgstr "Kunde ej reservera SCSI-id %(id_number)s" + +#: web.py:683 +#, python-format +msgid "Released the reservation for SCSI ID %(id_number)s" +msgstr "Avreserverade SCSI-id %(id_number)s" + +#: web.py:686 +#, python-format +msgid "Failed to release the reservation for SCSI ID %(id_number)s" +msgstr "Kunde ej avreservera SCSI-id %(id_number)s" + +#: web.py:724 +#, python-format +msgid "Saved image as: %(file_name)s" +msgstr "Sparade bildfilen som %(file_name)s" + +#: web.py:726 +#, python-format +msgid "Failed to create CD-ROM image from %(url)s" +msgstr "Kunde ej skapa CD-ROM-bildfil från %(url)s" + +#: web.py:732 +#, python-format +msgid "Attached to SCSI ID %(id_number)s" +msgstr "Anslöt till SCSI-id %(id_number)s" + +#: web.py:735 +#, python-format +msgid "" +"Failed to attach image to SCSI ID %(id_number)s. Try attaching it " +"manually." +msgstr "" +"Kunde ej ansluta bildfilen till SCSI-id %(id_number)s. Försök ansluta den" +" manuellt." + +#: web.py:754 web.py:771 +#, python-format +msgid "Failed to download file from %(url)s" +msgstr "Kunde ej ladda ner filen från %(url)s" + +#: web.py:802 +msgid "The file already exists!" +msgstr "Filen existerar redan!" + +#: web.py:810 +msgid "Unable to write the file to disk!" +msgstr "Kunde ej skriva filen till skivan!" + +#: web.py:821 +msgid "Transferred file corrupted!" +msgstr "Den överförda filen är skadad!" + +#: web.py:827 +msgid "File upload successful!" +msgstr "Filen har laddas upp!" + +#: web.py:870 +#, python-format +msgid "Image file deleted: %(file_name)s" +msgstr "Skivbildfilen %(file_name)s har blivit raderad" + +#: web.py:900 +#, python-format +msgid "Image file renamed to: %(file_name)s" +msgstr "Skivbildfilen har blivit omdöpt till %(file_name)s" + +#: web.py:937 +msgid "Aborted unzip: File(s) with the same name already exists." +msgstr "Uppackning stoppad: En eller flera filer med samma namn existerar." + +#: web.py:939 +msgid "Unzipped the following files:" +msgstr "Packade up dessa filer:" + +#: web.py:943 +#, python-format +msgid "Properties file(s) have been moved to %(directory)s" +msgstr "En eller flera egenskapsfiler har blivit flyttade till %(directory)s" + +#: web.py:946 +#, python-format +msgid "Failed to unzip %(zip_file)s" +msgstr "Kunde ej packa up %(zip_file)s" + +#: templates/base.html:29 +msgid "" +" This process may take a while, and will continue in the background if " +"you navigate away from this page." +msgstr "" +" Denna process kan ta ett tag, och kommer att forsätta i bakgrunden om du" +" navigerar bort från den här sidan." + +#: templates/base.html:34 +msgid "" +" The Web Interface will become unresponsive momentarily. Reload this page" +" after the Pi has started up again." +msgstr "" +" Webbgränssnittet kommer att sluta reagera nu. Ladda om den här sidan " +"efter systemet har startat igen." + +#: templates/base.html:48 +#, python-format +msgid "Logged in as %(username)s" +msgstr "Inloggad som %(username)s" + +#: templates/base.html:48 +msgid "Log Out" +msgstr "Logga ut" + +#: templates/base.html:52 +msgid "Log In to Use Web Interface" +msgstr "Logga in för att använda webbgränssnittet" + +#: templates/base.html:53 +msgid "Username" +msgstr "Användarnamn" + +#: templates/base.html:54 +msgid "Password" +msgstr "Lösenord" + +#: templates/base.html:60 +msgid "Web Interface Authentication Disabled" +msgstr "Webbgränssnittets autensisering är avstängt" + +#: templates/base.html:60 +msgid "See Wiki for more information" +msgstr "" +"Referera till wikin för vidare " +"information" + +#: templates/base.html:87 +msgid "RaSCSI version: " +msgstr "RaSCSI-version: " + +#: templates/base.html:88 +msgid "Pi environment: " +msgstr "Pi-miljö: " + +#: templates/drives.html:4 templates/drives.html:139 +msgid "Cancel" +msgstr "Avbryt" + +#: templates/drives.html:5 +msgid "Disclaimer" +msgstr "Notis" + +#: templates/drives.html:6 +#, python-format +msgid "" +"These device profiles are provided as-is with no guarantee to work " +"equally to the actual physical device they are named after. You may need " +"to provide appropirate device drivers and/or configuration parameters for" +" them to function properly. If you would like to see data modified, or " +"have additional devices to add to the list, please raise an issue ticket " +"at GitHub." +msgstr "" +"Det finns ingen garanti att dessa enhetsprofiler fungerar lika väl som de" +" egentliga enheterna de delar namn med. Du bör antagligen skaffa " +"drivrutiner och ange vissa parametrar för att de ska fungera korrekt. Om " +"du ser något som bör ändras, eller om du har andra enhetsprofiler som du " +"skulle vilja lägga till, kontakta oss via GitHub." + +#: templates/drives.html:7 +msgid "Hard Drives" +msgstr "Hårddiskar" + +#: templates/drives.html:12 templates/drives.html:56 templates/drives.html:102 +msgid "Name" +msgstr "Namn" + +#: templates/drives.html:13 templates/drives.html:57 templates/drives.html:103 +msgid "Size (MB)" +msgstr "Storlek (MB)" + +#: templates/drives.html:14 templates/drives.html:58 templates/drives.html:104 +msgid "Description" +msgstr "Beskrivning" + +#: templates/drives.html:15 templates/drives.html:59 templates/drives.html:105 +msgid "Ref." +msgstr "Ref." + +#: templates/drives.html:16 templates/drives.html:60 templates/drives.html:106 +msgid "Action" +msgstr "Handling" + +#: templates/drives.html:25 templates/drives.html:69 templates/drives.html:115 +msgid "Link" +msgstr "Länk" + +#: templates/drives.html:39 templates/drives.html:129 +msgid "Save as:" +msgstr "Spara som:" + +#: templates/drives.html:41 templates/drives.html:88 templates/drives.html:131 +#: templates/index.html:531 +msgid "Create" +msgstr "Skapa" + +#: templates/drives.html:51 +msgid "CD-ROM Drives" +msgstr "Cd-enheter" + +#: templates/drives.html:52 +msgid "" +"This will create a properties file for the given CD-ROM image. No new " +"image file will be created." +msgstr "" +"Denna funktion skapar endast en egenskapsfil för en cd-bildfil. Ingen ny " +"cd-bildfil kommer att skapas." + +#: templates/drives.html:80 +msgid "Create for:" +msgstr "Skapa för:" + +#: templates/drives.html:98 +msgid "Removable Drives" +msgstr "Uttagbara enheter" + +#: templates/drives.html:138 templates/index.html:295 +#, python-format +msgid "%(disk_space)s MB disk space remaining on the Pi" +msgstr "%(disk_space)s MB återstår på Pi-systemets skiva" + +#: templates/index.html:6 +msgid "Current RaSCSI Configuration" +msgstr "Nuvarande RaSCSI-konfiguration" + +#: templates/index.html:9 +msgid "Displays the currently attached devices for each available SCSI ID." +msgstr "Visar de nuvarande ansluna enheterna för varje SCSI-id" + +#: templates/index.html:10 +#, python-format +msgid "" +"Save and load device configurations, stored as json files in " +"%(config_dir)s" +msgstr "" +"Spara och ladda enhetskonfigurationer. Sparas som json-format i " +"%(config_dir)s" + +#: templates/index.html:11 +msgid "" +"To have a particular device configuration load when RaSCSI starts, save " +"it as default." +msgstr "" +"Om du sparar en enhetskonfiguration som default så laddas den " +"när RaSCSI startar." + +#: templates/index.html:25 +msgid "No saved configurations" +msgstr "Inga sparade konfigurationer" + +#: templates/index.html:29 +msgid "Load" +msgstr "Ladda" + +#: templates/index.html:29 +msgid "Detach all current device and Load configuration?" +msgstr "Koppla ifrån alla enheter och ladda konfigurationen?" + +#: templates/index.html:30 templates/index.html:287 +msgid "Delete" +msgstr "Radera" + +#: templates/index.html:30 +msgid "Delete configuration file?" +msgstr "Radera konfigurationsfilen?" + +#: templates/index.html:35 +msgid "Save" +msgstr "Spara" + +#: templates/index.html:41 templates/index.html:252 +msgid "ID" +msgstr "Id" + +#: templates/index.html:43 templates/index.html:260 +msgid "LUN" +msgstr "LUN" + +#: templates/index.html:45 templates/index.html:268 +msgid "Type" +msgstr "Typ" + +#: templates/index.html:46 +msgid "Status" +msgstr "Status" + +#: templates/index.html:47 templates/index.html:166 +msgid "File" +msgstr "Fil" + +#: templates/index.html:48 +msgid "Product" +msgstr "Produkt" + +#: templates/index.html:49 templates/index.html:168 +msgid "Actions" +msgstr "Handlingar" + +#: templates/index.html:84 templates/index.html:277 templates/index.html:337 +msgid "Attach" +msgstr "Anslut" + +#: templates/index.html:98 +msgid "Eject Disk? WARNING: On Mac OS, eject the Disk in the Finder instead!" +msgstr "Mata ut skiva? VARNING: På Mac OS, mata ut skivan i Finder istället!" + +#: templates/index.html:101 +msgid "Eject" +msgstr "Mata ut" + +#: templates/index.html:104 +msgid "Detach Device?" +msgstr "Koppla ifrån enhet?" + +#: templates/index.html:107 +msgid "Detach" +msgstr "Koppla av" + +#: templates/index.html:113 +msgid "Info" +msgstr "Info" + +#: templates/index.html:116 +msgid "Enter a memo for this reservation" +msgstr "Skriv ett memo för reservationen" + +#: templates/index.html:119 +msgid "Reserve" +msgstr "Reservera" + +#: templates/index.html:129 +msgid "Reserved ID" +msgstr "Reserverat id" + +#: templates/index.html:135 +msgid "Unreserve" +msgstr "Avreservera" + +#: templates/index.html:144 +msgid "Detach all SCSI Devices?" +msgstr "Koppla ifrån alla SCSI-enheter?" + +#: templates/index.html:145 +msgid "Detach All Devices" +msgstr "Koppla ifrån alla enheter" + +#: templates/index.html:152 +msgid "Image File Management" +msgstr "Filhanterare" + +#: templates/index.html:155 +#, python-format +msgid "" +"Manage image files in the active RaSCSI image directory: " +"%(directory)s with a scan depth of %(scan_depth)s." +msgstr "" +"Hantera filer i den aktiva skivbildsfilskatalogen: %(directory)s" +" med hierarkiskt djup %(scan_depth)s." + +#: templates/index.html:156 +#, python-format +msgid "" +"Select a valid SCSI ID and LUN to attach to. " +"Unless you know what you're doing, always use LUN 0." +msgstr "" +"Välj ett giltigt SCSI-id samt LUN att ansluta " +"till. Om du inte har särskild orsak, använd alltid LUN 0." + +#: templates/index.html:158 +msgid "" +"If RaSCSI was unable to detect the device type associated with the image," +" you can choose the type from the dropdown." +msgstr "" +"Om RaSCSI inte kunde detektera enhetstypen för en skivbild, kan du välja " +"en enhetstyp från rullgardinsmenyn." + +#: templates/index.html:159 +msgid "" +"Types: SAHD = SASI HDD | SCHD = SCSI HDD | SCRM = Removable | SCMO = " +"Magneto-Optical | SCCD = CD-ROM | SCBR = Host Bridge | SCDP = DaynaPORT" +msgstr "" +"Typer: SAHD = SASI-hårddisk | SCHD = SCSI-hårddisk | SCRM = utmatsbar | " +"SCMO = magnet optisk | SCCD = cd-rom | SCBR = Host Bridge | SCDP = " +"DaynaPORT" + +#: templates/index.html:167 +msgid "Size" +msgstr "Storlek" + +#: templates/index.html:184 +msgid "Properties File" +msgstr "Egenskapsfil" + +#: templates/index.html:204 templates/index.html:218 +msgid "Unzip" +msgstr "Packa upp" + +#: templates/index.html:204 templates/index.html:218 +msgid "Unzipping a single file..." +msgstr "Packar upp en enda fil..." + +#: templates/index.html:233 templates/index.html:530 +msgid "MB" +msgstr "MB" + +#: templates/index.html:239 +msgid "Attached!" +msgstr "Ansluten!" + +#: templates/index.html:246 +msgid "Unzip All" +msgstr "Packa upp allt" + +#: templates/index.html:246 +msgid "Unzipping all files..." +msgstr "Packar upp alla filer..." + +#: templates/index.html:280 +#, python-format +msgid "Enter new file name for: %(file_name)s" +msgstr "Ange ett nytt filnamn åt %(file_name)s" + +#: templates/index.html:283 +msgid "Rename" +msgstr "Döp om" + +#: templates/index.html:285 +#, python-format +msgid "Delete file: %(file_name)s?" +msgstr "Radera filen %(file_name)s?" + +#: templates/index.html:301 +msgid "Attach Ethernet Adapter" +msgstr "Anslut ethernet-adapter" + +#: templates/index.html:304 +#, python-format +msgid "" +"Emulates a SCSI DaynaPORT Ethernet Adapter. Host " +"drivers and configuration required." +msgstr "" +"Emulerar en SCSI DaynaPORT ethernet-adapter. Kräver " +"drivrutiner och inställningar." + +#: templates/index.html:306 +msgid "" +"If you have a DHCP setup, choose only the interface you have configured " +"the bridge with. You can ignore the Static IP fields when attaching." +msgstr "" +"Om du använder DHCP, välj bara nätverskgränssnittet som bryggan är " +"konfigurerad med. Du kan ignorera fälten för statisk adress." + +#: templates/index.html:307 +#, python-format +msgid "" +"Configure the network bridge by running easyinstall.sh, or follow the manual steps in the wiki." +msgstr "" +"Konfigurera nätverksbryggan med hjälp av easyinstall.sh, eller följ " +"instruktionerna i wikin." + +#: templates/index.html:310 +msgid "" +"The rascsi_bridge interface is active and ready to be used by " +"DaynaPORT!" +msgstr "" +"Nätverksbryggan rascsi_bridge är påslagen och kan användas av " +"DaynaPORT!" + +#: templates/index.html:318 +msgid "Interface:" +msgstr "Gränssnitt:" + +#: templates/index.html:326 +msgid "Static IP (optional):" +msgstr "Statisk adress (tillval):" + +#: templates/index.html:329 templates/index.html:455 +msgid "SCSI ID:" +msgstr "SCSI-id:" + +#: templates/index.html:343 +#, python-format +msgid "Macproxy is running at %(ip_addr)s (default port 5000)" +msgstr "Macproxy är tillgängligt på %(ip_addr)s (förvald port 5000)" + +#: templates/index.html:345 +#, python-format +msgid "" +"Install Macproxy to browse the Web with any " +"vintage browser. It's not just for Macs!" +msgstr "" +"Installera Macproxy och surfa på nätet med gamla " +"webbläsare. Den är inte bara för Macar!" + +#: templates/index.html:351 +msgid "Upload File" +msgstr "Ladda up fil" + +#: templates/index.html:354 +#, python-format +msgid "" +"Uploads file to %(directory)s. The largest file size accepted is" +" %(max_file_size)s MB." +msgstr "" +"Ladda upp fil till %(directory)s. Den största tillåtna " +"filstorleken är %(max_file_size)s MB." + +#: templates/index.html:355 +msgid "" +"For unrecognized file types, try renaming hard drive images to '.hds', " +"CD-ROM images to '.iso', and removable drive images to '.hdr' before " +"uploading." +msgstr "" +"Om en fil inte känns igen, du kan försöka döpa om hårddiskbildfiler till " +"'.hds', cd-bildfiler till '.iso', och uttagbara bildfiler till '.hdr' " +"innan du laddar upp den." + +#: templates/index.html:356 +#, python-format +msgid "Recognized file types: %(valid_file_suffix)s" +msgstr "Kända filtyper: %(valid_file_suffix)s" + +#: templates/index.html:383 +msgid "Download File to Images" +msgstr "Ladda ner fil till Images" + +#: templates/index.html:386 +#, python-format +msgid "Given a URL, download that file to the %(directory)s directory." +msgstr "Ta ett url och ladda ner en fil till katalogen %(directory)s" + +#: templates/index.html:394 templates/index.html:420 templates/index.html:464 +msgid "URL:" +msgstr "Url:" + +#: templates/index.html:395 templates/index.html:421 templates/index.html:465 +msgid "URL" +msgstr "Url" + +#: templates/index.html:396 templates/index.html:422 +msgid "Download" +msgstr "Ladda ner" + +#: templates/index.html:396 +msgid "Downloading File to Images..." +msgstr "Laddar ner filen till Images..." + +#: templates/index.html:406 +msgid "Download File to AppleShare" +msgstr "Ladda ner fil till AppleShare" + +#: templates/index.html:409 +#, python-format +msgid "" +"Given a URL, download that file to the %(directory)s directory " +"and share it over AFP." +msgstr "" +"Ta ett url och ladda ner en fil till katalogen %(directory)s och" +" fildela den över AFP." + +#: templates/index.html:410 +msgid "Manage the files you download here through AppleShare on your vintage Mac." +msgstr "Hantera dessa filer via AppleShare på en klassisk Mac." + +#: templates/index.html:411 +#, python-format +msgid "" +"Requires Netatalk to be installed and configured " +"correctly for your network." +msgstr "" +"Kräver att Netatalk är installerat och inställt " +"på lämpligt vis för ditt nätverk." + +#: templates/index.html:422 +msgid "Downloading File to AppleShare..." +msgstr "Laddar ner fil till AppleShare..." + +#: templates/index.html:429 +msgid "The AppleShare server is running. No active connections." +msgstr "AppleShare-servern är aktiv. Inga klienter är anslutna." + +#: templates/index.html:431 +#, python-format +msgid "%(value)d active AFP connection" +msgstr "%(value)d aktiv AFP-klient" + +#: templates/index.html:433 +#, python-format +msgid "%(value)d active AFP connections" +msgstr "%(value)d aktiva AFP-klienter" + +#: templates/index.html:436 +#, python-format +msgid "" +"Install Netatalk to use the AppleShare File " +"Server." +msgstr "" +"Installera Netatalk innan du kan använda " +"AppleShare-fildelning." + +#: templates/index.html:443 +msgid "Download File and Create CD-ROM image" +msgstr "Ladda ner fil och skapa en cd-bildfil" + +#: templates/index.html:446 +msgid "" +"Create an ISO file system CD-ROM image with the downloaded file, and " +"mount it on the given SCSI ID." +msgstr "" +"Skapar en cd-bildfil med ISO-filsystem som innehåller den nedladdade " +"filen. Sedan ansluts den till det angivna SCSI-idt." + +#: templates/index.html:447 +msgid "HFS is for Mac OS, Joliet for Windows, and Rock Ridge for POSIX." +msgstr "HFS är för Mac OS, Joliet för Windows, samt Rock Ridge för POSIX." + +#: templates/index.html:448 +#, python-format +msgid "On Mac OS, a compatible CD-ROM driver is required." +msgstr "På Mac OS krävs kompatibla cd-drivrutiner." + +#: templates/index.html:449 +msgid "" +"If the downloaded file is a zip archive, we will attempt to unzip it and " +"store the resulting files." +msgstr "" +"Om den nedladdade filen är en zip-fil så försöker vi packa up den och " +"spara de uppackade filerna på cd-bildfilen" + +#: templates/index.html:466 templates/index.html:511 +msgid "Type:" +msgstr "Typ:" + +#: templates/index.html:487 +msgid "Download and Mount CD-ROM image" +msgstr "Ladda ner och mata in cd-bildfil" + +#: templates/index.html:487 +msgid "Downloading File and generating CD-ROM image..." +msgstr "Laddar ner fil och tillverkar cd-bildfil..." + +#: templates/index.html:497 +msgid "Create Empty Disk Image File" +msgstr "Skapa en tom skivbilsdfil" + +#: templates/index.html:500 +msgid "The Generic image type is recommended for most computer platforms." +msgstr "Bildfilsformatet 'Generic' är rekommederad för de flesta datorsystem." + +#: templates/index.html:501 +msgid "" +"APPLE GENUINE (.hda) and NEC GENUINE (.hdn) image types will make RaSCSI " +"behave as a particular drive type that are recognized by Mac and PC98 " +"systems, respectively." +msgstr "" +"Bildfilsformaten APPLE GENUINE (.hda) samt NEC GENUINE (.hdn) gör så att " +"RaSCSI beter sig som en typ av hårddisk som Macar och PC98-datorer känner" +" igen." + +#: templates/index.html:502 +msgid "" +"SASI images should only be used on the original Sharp X68000, or other " +"legacy systems that utilize this pre-SCSI standard." +msgstr "" +"Bildfilsformatet SASI bör endast användas för den första Sharp " +"X68000-modellen eller andra riktigt gamla system som använder denna " +"föregångare till SCSI." + +#: templates/index.html:509 +msgid "File Name:" +msgstr "Filnamn:" + +#: templates/index.html:510 +msgid "File Name" +msgstr "Filnamn" + +#: templates/index.html:514 +msgid "SCSI Hard Disk image (Generic) [.hds]" +msgstr "SCSI-hårddisk (Generic) [.hds]" + +#: templates/index.html:517 +msgid "SCSI Hard Disk image (APPLE GENUINE) [.hda]" +msgstr "SCSI-hårddisk (APPLE GENUINE) [.hda]" + +#: templates/index.html:520 +msgid "SCSI Hard Disk image (NEC GENUINE) [.hdn]" +msgstr "SCSI-hårddisk (NEC GENUINE) [.hdn]" + +#: templates/index.html:523 +msgid "SCSI Removable Media Disk image (Generic) [.hdr]" +msgstr "SCSI utmatbart medium (Generic) [.hdr]" + +#: templates/index.html:526 +msgid "SASI Hard Disk image (Legacy) [.hdf]" +msgstr "SASI-hårddisk (föråldrat) [.hdf]" + +#: templates/index.html:529 +msgid "Size:" +msgstr "Storlek:" + +#: templates/index.html:541 +msgid "Create Named Drive" +msgstr "Skapa benämnd skiva" + +#: templates/index.html:544 +msgid "" +"Create pairs of images and properties files from a list of real-life " +"drives." +msgstr "" +"Skapar ett par av skivbilds- och egenskapsfiler från en lista av verkliga" +" enheter." + +#: templates/index.html:545 +msgid "" +"This will make RaSCSI use certain vendor strings and block sizes that may" +" improve compatibility with certain systems." +msgstr "" +"På så vis kommer RaSCSI använda vissa tillverkarattribut och " +"blockstorlekar som kan hjälpa till med kompatibilitet." + +#: templates/index.html:548 +msgid "Create a named disk image that mimics real-life drives" +msgstr "Skapa en benämnd skivbildfil som låstas vara en riktig enhet" + +#: templates/index.html:554 +msgid "Logging" +msgstr "Loggar" + +#: templates/index.html:557 +msgid "Fetch a certain number of lines of system logs with the given scope." +msgstr "Skaffar ett visst antal loggar för en viss systemprocess." + +#: templates/index.html:564 +msgid "Log Lines:" +msgstr "Antal loggar:" + +#: templates/index.html:566 +msgid "Scope:" +msgstr "Process:" + +#: templates/index.html:578 +msgid "Show Logs" +msgstr "Skaffa loggar" + +#: templates/index.html:588 +msgid "Server Log Level" +msgstr "Serverns loggnivå" + +#: templates/index.html:591 +msgid "Change the log level of the RaSCSI backend process." +msgstr "Ändra loggnivån för RaSCSI-servern" + +#: templates/index.html:592 +msgid "The current dropdown selection indicates the active log level." +msgstr "Det nuvarande valet i rullgardinsmenyn påvisar aktiv loggnivå." + +#: templates/index.html:599 +msgid "Log Level:" +msgstr "Loggnivå:" + +#: templates/index.html:607 +msgid "Set Log Level" +msgstr "Ställ in loggnivå" + +#: templates/index.html:617 +msgid "Raspberry Pi Operations" +msgstr "Raspberry Pi-kommandon" + +#: templates/index.html:620 +msgid "Reboot or shut down the Raspberry Pi that RaSCSI is running on." +msgstr "Starta om eller stäng av Raspberry Pi-systemet som RaSCSI körs på." + +#: templates/index.html:621 +msgid "" +"IMPORTANT: Always shut down the Pi before turning off the power. Failing " +"to do so may lead to data loss." +msgstr "" +"VIKTIGT: Stäng alltid av Pi-systemet innan du stänger av strömmen. Det " +"finns risk för dataförlust." + +#: templates/index.html:627 +msgid "Reboot the Raspberry Pi?" +msgstr "Vill du starta om din Raspberry Pi?" + +#: templates/index.html:627 +msgid "Rebooting the Raspberry Pi..." +msgstr "Startar om Raspberry Pi..." + +#: templates/index.html:628 +msgid "Reboot Raspberry Pi" +msgstr "Starta om Raspberry Pi" + +#: templates/index.html:632 +msgid "Shut down the Raspberry Pi?" +msgstr "Vill du stänga av din Raspberry Pi?" + +#: templates/index.html:632 +msgid "Shutting down the Raspberry Pi..." +msgstr "Stänger av Raspberry Pi..." + +#: templates/index.html:633 +msgid "Shut Down Raspberry Pi" +msgstr "Stäng av Raspberry Pi" + +#~ msgid "Saved config to %(file_name)" +#~ msgstr "Sparade konfigurationsfilen som %(file_name)s" + diff --git a/src/web/web.py b/src/web/web.py index 723c6af7..2559a1c2 100644 --- a/src/web/web.py +++ b/src/web/web.py @@ -21,6 +21,7 @@ from flask import ( session, abort, ) +from flask_babel import Babel, _ from file_cmds import ( list_images, @@ -81,6 +82,11 @@ from settings import ( ) APP = Flask(__name__) +BABEL = Babel(APP) + +@BABEL.localeselector +def get_locale(): + return request.accept_languages.best_match(["en", "de", "sv"]) @APP.route("/") def index(): @@ -88,7 +94,7 @@ def index(): Sets up data structures for and renders the index page """ if not is_token_auth()["status"] and not APP.config["TOKEN"]: - abort(403, "RaSCSI is password protected. Start the Web Interface with the --password parameter.") + abort(403, _(u"RaSCSI is password protected. Start the Web Interface with the --password parameter.")) server_info = get_server_info() disk = disk_space() @@ -182,7 +188,7 @@ def drive_list(): return redirect(url_for("index")) conf = process["conf"] else: - flash("Could not read drive properties from " + str(drive_properties), "error") + flash(_("Could not read drive properties from %(properties_file)s", properties_file=drive_properties), "error") return redirect(url_for("index")) hd_conf = [] @@ -246,7 +252,7 @@ def login(): if authenticate(str(username), str(password)): session["username"] = request.form["username"] return redirect(url_for("index")) - flash(f"You must log in with credentials for a user in the '{AUTH_GROUP}' group!", "error") + flash(_(u"You must log in with credentials for a user in the '%(group)s' group", group=AUTH_GROUP), "error") return redirect(url_for("index")) @@ -294,11 +300,12 @@ def drive_create(): size = request.form.get("size") file_type = request.form.get("file_type") file_name = request.form.get("file_name") + full_file_name = file_name + "." + file_type # Creating the image file process = create_new_image(file_name, file_type, size) if process["status"]: - flash(f"Created drive image file: {file_name}.{file_type}") + flash(_(u"Image file created: %(file_name)s", file_name=full_file_name)) else: flash(process["msg"], "error") return redirect(url_for("index")) @@ -392,6 +399,7 @@ def config_load(): flash(process['msg'], "error") return redirect(url_for("index")) + # The only reason we would reach here would be a Web UI bug. Will not localize. flash("Got an unhandled request (needs to be either load or delete)", "error") return redirect(url_for("index")) @@ -422,8 +430,7 @@ def show_logs(): headers = {"content-type": "text/plain"} return process.stdout.decode("utf-8"), int(lines), headers - flash("Failed to get logs") - flash(process.stdout.decode("utf-8"), "stdout") + flash(_(u"An error occurred when fetching logs.")) flash(process.stderr.decode("utf-8"), "stderr") return redirect(url_for("index")) @@ -438,10 +445,10 @@ def log_level(): process = set_log_level(level) if process["status"]: - flash(f"Log level set to {level}") + flash(_(u"Log level set to %(value)s", value=level)) return redirect(url_for("index")) - flash(f"Failed to set log level to {level}!", "error") + flash(process["msg"], "error") return redirect(url_for("index")) @@ -456,22 +463,26 @@ def daynaport_attach(): ip_addr = request.form.get("ip") mask = request.form.get("mask") - error_msg = ("Please follow the instructions at " - "https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link") + error_url = "https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link" + error_msg = _(u"Please follow the instructions at %(url)s", url=error_url) if interface.startswith("wlan"): if not introspect_file("/etc/sysctl.conf", r"^net\.ipv4\.ip_forward=1$"): - flash("IPv4 forwarding is not enabled. " + error_msg, "error") + flash(_(u"Configure IPv4 forwarding before using a wireless network device."), "error") + flash(error_msg, "error") return redirect(url_for("index")) if not Path("/etc/iptables/rules.v4").is_file(): - flash("NAT has not been configured. " + error_msg, "error") + flash(_(u"Configure NAT before using a wireless network device."), "error") + flash(error_msg, "error") return redirect(url_for("index")) else: if not introspect_file("/etc/dhcpcd.conf", r"^denyinterfaces " + interface + r"$"): - flash("The network bridge hasn't been configured. " + error_msg, "error") + flash(_(u"Configure the network bridge before using a wired network device."), "error") + flash(error_msg, "error") return redirect(url_for("index")) if not Path("/etc/network/interfaces.d/rascsi_bridge").is_file(): - flash("The network bridge hasn't been configured. " + error_msg, "error") + flash(_(u"Configure the network bridge before using a wired network device."), "error") + flash(error_msg, "error") return redirect(url_for("index")) kwargs = {"device_type": "SCDP"} @@ -483,7 +494,7 @@ def daynaport_attach(): process = attach_image(scsi_id, **kwargs) if process["status"]: - flash(f"Attached DaynaPORT to SCSI ID {scsi_id}!") + flash(_(u"Attached DaynaPORT to SCSI ID %(id_number)s", id_number=scsi_id)) return redirect(url_for("index")) flash(process["msg"], "error") @@ -531,14 +542,17 @@ def attach(): process = attach_image(scsi_id, **kwargs) if process["status"]: - flash(f"Attached {file_name} to SCSI ID {scsi_id} LUN {unit}") + flash(_(u"Attached %(file_name)s to SCSI ID %(id_number)s LUN %(unit_number)s", + file_name=file_name, id_number=scsi_id, unit_number=unit)) if int(file_size) % int(expected_block_size): - flash(f"The image file size {file_size} bytes is not a multiple of " - f"{expected_block_size} and RaSCSI will ignore the trailing data. " - f"The image may be corrupted so proceed with caution.", "error") + flash(_(u"The image file size %(file_size)s bytes is not a multiple of " + u"%(block_size)s. RaSCSI will ignore the trailing data. " + u"The image may be corrupted, so proceed with caution.", + file_size=file_size, block_size=expected_block_size), "error") return redirect(url_for("index")) - flash(f"Failed to attach {file_name} to SCSI ID {scsi_id} LUN {unit}", "error") + flash(_(u"Failed to attach %(file_name)s to SCSI ID %(id_number)s LUN %(unit_number)s", + file_name=file_name, id_number=scsi_id, unit_number=unit), "error") flash(process["msg"], "error") return redirect(url_for("index")) @@ -551,7 +565,7 @@ def detach_all_devices(): """ process = detach_all() if process["status"]: - flash("Detached all SCSI devices") + flash(_(u"Detached all SCSI devices")) return redirect(url_for("index")) flash(process["msg"], "error") @@ -568,10 +582,12 @@ def detach(): unit = request.form.get("unit") process = detach_by_id(scsi_id, unit) if process["status"]: - flash(f"Detached SCSI ID {scsi_id} LUN {unit}") + flash(_(u"Detached SCSI ID %(id_number)s LUN %(unit_number)s", + id_number=scsi_id, unit_number=unit)) return redirect(url_for("index")) - flash(f"Failed to detach SCSI ID {scsi_id} LUN {unit}", "error") + flash(_(u"Failed to detach %(file_name)s from SCSI ID %(id_number)s LUN %(unit_number)s", + file_name=file_name, id_number=scsi_id, unit_number=unit), "error") flash(process["msg"], "error") return redirect(url_for("index")) @@ -587,10 +603,12 @@ def eject(): process = eject_by_id(scsi_id, unit) if process["status"]: - flash(f"Ejected SCSI ID {scsi_id} LUN {unit}") + flash(_(u"Ejected SCSI ID %(id_number)s LUN %(unit_number)s", + id_number=scsi_id, unit_number=unit)) return redirect(url_for("index")) - flash(f"Failed to eject SCSI ID {scsi_id} LUN {unit}", "error") + flash(_(u"Failed to eject %(file_name)s from SCSI ID %(id_number)s LUN %(unit_number)s", + file_name=file_name, id_number=scsi_id, unit_number=unit), "error") flash(process["msg"], "error") return redirect(url_for("index")) @@ -612,18 +630,19 @@ def device_info(): # the one and only device that should have been returned device = devices["device_list"][0] if str(device["id"]) == scsi_id: - flash("=== DEVICE INFO ===") - flash(f"SCSI ID: {device['id']}") - flash(f"LUN: {device['unit']}") - flash(f"Type: {device['device_type']}") - flash(f"Status: {device['status']}") - flash(f"File: {device['image']}") - flash(f"Parameters: {device['params']}") - flash(f"Vendor: {device['vendor']}") - flash(f"Product: {device['product']}") - flash(f"Revision: {device['revision']}") - flash(f"Block Size: {device['block_size']} bytes") - flash(f"Image Size: {device['size']} bytes") + flash(_(u"DEVICE INFO")) + flash("===========") + flash(_(u"SCSI ID: %(id_number)s", id_number=device["id"])) + flash(_(u"LUN: %(unit_number)s", unit_number=device["unit"])) + flash(_(u"Type: %(device_type)s", device_type=device["device_type"])) + flash(_(u"Status: %(device_status)s", device_status=device["status"])) + flash(_(u"File: %(image_file)s", image_file=device["image"])) + flash(_(u"Parameters: %(value)s", value=device["params"])) + flash(_(u"Vendor: %(value)s", value=device["vendor"])) + flash(_(u"Product: %(value)s", value=device["product"])) + flash(_(u"Revision: %(revision_number)s", revision_number=device["revision"])) + flash(_(u"Block Size: %(value)s bytes", value=device["block_size"])) + flash(_(u"Image Size: %(value)s bytes", value=device["size"])) return redirect(url_for("index")) flash(devices["msg"], "error") @@ -642,9 +661,10 @@ def reserve_id(): process = reserve_scsi_ids(reserved_ids) if process["status"]: RESERVATIONS[int(scsi_id)] = memo - flash(f"Reserved SCSI ID {scsi_id}") + flash(_(u"Reserved SCSI ID %(id_number)s", id_number=scsi_id)) return redirect(url_for("index")) + flash(_(u"Failed to reserve SCSI ID %(id_number)s", id_number=scsi_id)) flash(process["msg"], "error") return redirect(url_for("index")) @@ -660,9 +680,10 @@ def unreserve_id(): process = reserve_scsi_ids(reserved_ids) if process["status"]: RESERVATIONS[int(scsi_id)] = "" - flash(f"Released the reservation for SCSI ID {scsi_id}") + flash(_(u"Released the reservation for SCSI ID %(id_number)s", id_number=scsi_id)) return redirect(url_for("index")) + flash(_(u"Failed to release the reservation for SCSI ID %(id_number)s", id_number=scsi_id)) flash(process["msg"], "error") return redirect(url_for("index")) @@ -700,18 +721,19 @@ def download_to_iso(): process = download_file_to_iso(url, *iso_args) if process["status"]: flash(process["msg"]) - flash(f"Saved image as: {process['file_name']}") + flash(_(u"Saved image as: %(file_name)s", file_name=process['file_name'])) else: - flash(f"Failed to create CD-ROM image from {url}", "error") + flash(_(u"Failed to create CD-ROM image from %(url)s", url), "error") flash(process["msg"], "error") return redirect(url_for("index")) process_attach = attach_image(scsi_id, device_type="SCCD", image=process["file_name"]) if process_attach["status"]: - flash(f"Attached to SCSI ID {scsi_id}") + flash(_(u"Attached to SCSI ID %(id_number)s", id_number=scsi_id)) return redirect(url_for("index")) - flash(f"Failed to attach image to SCSI ID {scsi_id}. Try attaching it manually.", "error") + flash(_(u"Failed to attach image to SCSI ID %(id_number)s. Try attaching it manually.", + id_number=scsi_id), "error") flash(process_attach["msg"], "error") return redirect(url_for("index")) @@ -729,7 +751,7 @@ def download_img(): flash(process["msg"]) return redirect(url_for("index")) - flash(f"Failed to download file {url}", "error") + flash(_(u"Failed to download file from %(url)s", url), "error") flash(process["msg"], "error") return redirect(url_for("index")) @@ -746,7 +768,7 @@ def download_afp(): flash(process["msg"]) return redirect(url_for("index")) - flash(f"Failed to download file {url}", "error") + flash(_(u"Failed to download file from %(url)s", url), "error") flash(process["msg"], "error") return redirect(url_for("index")) @@ -766,26 +788,26 @@ def upload_file(): from os import path log = logging.getLogger("pydrop") - file = request.files["file"] - filename = secure_filename(file.filename) + file_object = request.files["file"] + file_name = secure_filename(file_object.filename) server_info = get_server_info() - save_path = path.join(server_info["image_dir"], filename) + save_path = path.join(server_info["image_dir"], file_name) current_chunk = int(request.form['dzchunkindex']) # Makes sure not to overwrite an existing file, # but continues writing to a file transfer in progress if path.exists(save_path) and current_chunk == 0: - return make_response((f"The file {file.filename} already exists!", 400)) + return make_response(_(u"The file already exists!"), 400) try: with open(save_path, "ab") as save: save.seek(int(request.form["dzchunkbyteoffset"])) - save.write(file.stream.read()) + save.write(file_object.stream.read()) except OSError: log.exception("Could not write to file") - return make_response(("Unable to write the file to disk!", 500)) + return make_response(_(u"Unable to write the file to disk!"), 500) total_chunks = int(request.form["dztotalchunkcount"]) @@ -795,14 +817,14 @@ def upload_file(): log.error("Finished transferring %s, " "but it has a size mismatch with the original file." "Got %s but we expected %s.", - file.filename, path.getsize(save_path), request.form['dztotalfilesize']) - return make_response(("Transferred file corrupted!", 500)) + file_object.filename, path.getsize(save_path), request.form['dztotalfilesize']) + return make_response(_(u"Transferred file corrupted!"), 500) - log.info("File %s has been uploaded successfully", file.filename) + log.info("File %s has been uploaded successfully", file_object.filename) log.debug("Chunk %s of %s for file %s completed.", - current_chunk + 1, total_chunks, file.filename) + current_chunk + 1, total_chunks, file_object.filename) - return make_response(("File upload successful!", 200)) + return make_response(_(u"File upload successful!"), 200) @APP.route("/files/create", methods=["POST"]) @@ -814,10 +836,11 @@ def create_file(): file_name = request.form.get("file_name") size = (int(request.form.get("size")) * 1024 * 1024) file_type = request.form.get("type") + full_file_name = file_name + "." + file_type process = create_new_image(file_name, file_type, size) if process["status"]: - flash(f"Drive image created: {file_name}.{file_type}") + flash(_(u"Image file created: %(file_name)s", file_name=full_file_name)) return redirect(url_for("index")) flash(process["msg"], "error") @@ -844,7 +867,7 @@ def delete(): process = delete_image(file_name) if process["status"]: - flash(f"Image file deleted: {file_name}") + flash(_(u"Image file deleted: %(file_name)s", file_name=file_name)) else: flash(process["msg"], "error") return redirect(url_for("index")) @@ -874,7 +897,7 @@ def rename(): process = rename_image(file_name, new_file_name) if process["status"]: - flash(f"Image file renamed to: {new_file_name}") + flash(_(u"Image file renamed to: %(file_name)s", file_name=new_file_name)) else: flash(process["msg"], "error") return redirect(url_for("index")) @@ -911,16 +934,16 @@ def unzip(): process = unzip_file(zip_file, zip_member, zip_members) if process["status"]: if not process["msg"]: - flash("Aborted unzip: File(s) with the same name already exists.", "error") + flash(_(u"Aborted unzip: File(s) with the same name already exists."), "error") return redirect(url_for("index")) - flash("Unzipped the following files:") + flash(_(u"Unzipped the following files:")) for msg in process["msg"]: flash(msg) if process["prop_flag"]: - flash(f"Properties file(s) have been moved to {CFG_DIR}") + flash(_(u"Properties file(s) have been moved to %(directory)s", directory=CFG_DIR)) return redirect(url_for("index")) - flash("Failed to unzip " + zip_file, "error") + flash(_(u"Failed to unzip %(zip_file)s", zip_file=zip_file), "error") flash(process["msg"], "error") return redirect(url_for("index")) @@ -928,8 +951,12 @@ def unzip(): @APP.before_first_request def load_default_config(): """ - Load the default configuration file, if found + Webapp initialization steps that require the Flask app to have started: + - Get the detected locale to use for localizations + - Load the default configuration file, if found """ + APP.config["LOCALE"] = get_locale() + logging.info("Detected locale: " + APP.config["LOCALE"]) if Path(f"{CFG_DIR}/{DEFAULT_CONFIG}").is_file(): read_config(DEFAULT_CONFIG) @@ -939,7 +966,7 @@ if __name__ == "__main__": APP.config["SESSION_TYPE"] = "filesystem" APP.config["MAX_CONTENT_LENGTH"] = int(MAX_FILE_SIZE) - parser = argparse.ArgumentParser(description="RaSCSI Web Interface arguments") + parser = argparse.ArgumentParser(description="RaSCSI Web Interface command line arguments") parser.add_argument( "--port", type=int,
-
- + +
-
- + +