First version of OctoPrint with i18n/l10n support
Also included is a translation for (informal) german. New languages can be added with "python setup.py babel_new --locale=<language code>" which will create the corresponding .po file from the existing .pot file under "src/octoprint/translations/<language code>". Translations can be refreshed from strings in source with "python setup.py babel_refresh". Existing translations can be compiled into usable translation files (.mo for python and .js for Javascript) via "python setup.py babel_compile". You'll need to install the development dependencies for all of this to work, just issue "pip install -r requirements-dev.txt" Note: numbers are not yet correctly formatted for their respective locale (e.g. "2.5mm" instead of "2,5mm" in german).
This commit is contained in:
parent
b78bb9e970
commit
53e52c841b
30 changed files with 3258 additions and 343 deletions
6
babel.cfg
Normal file
6
babel.cfg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[python: src/octoprint/**.py]
|
||||
[jinja2: src/octoprint/templates/**.jinja2]
|
||||
extensions=jinja2.ext.autoescape, jinja2.ext.with_
|
||||
|
||||
[javascript: src/octoprint/static/js/app/**.js]
|
||||
extract_messages = gettext, ngettext
|
||||
|
|
@ -2,3 +2,4 @@ mock>=1.0.1
|
|||
nose>=1.3.0
|
||||
sphinxcontrib-httpdomain
|
||||
sphinx_rtd_theme
|
||||
po2json
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ sockjs-tornado>=1.0.0
|
|||
PyYAML==3.10
|
||||
Flask-Login==0.2.2
|
||||
Flask-Principal==0.3.5
|
||||
Flask-Babel==0.9
|
||||
pyserial
|
||||
netaddr
|
||||
watchdog
|
||||
|
|
|
|||
101
setup.py
101
setup.py
|
|
@ -13,6 +13,15 @@ import os
|
|||
import shutil
|
||||
import glob
|
||||
|
||||
from babel.messages import frontend as babel
|
||||
import po2json
|
||||
|
||||
I18N_MAPPING_FILE = "babel.cfg"
|
||||
I18N_DOMAIN = "messages"
|
||||
I18N_INPUT_DIRS = "."
|
||||
I18N_OUTPUT_DIR_PY = os.path.join("src", "octoprint", "translations")
|
||||
I18N_OUTPUT_DIR_JS = os.path.join("src", "octoprint", "static", "js", "i18n")
|
||||
I18N_POT_FILE = os.path.join(I18N_OUTPUT_DIR_PY, "messages.pot")
|
||||
|
||||
def package_data_dirs(source, sub_folders):
|
||||
dirs = []
|
||||
|
|
@ -47,10 +56,100 @@ class CleanCommand(Command):
|
|||
shutil.rmtree(egg)
|
||||
|
||||
|
||||
class NewTranslation(Command):
|
||||
description = "create a new translation"
|
||||
user_options = [
|
||||
('locale=', 'l', 'locale for the new translation'),
|
||||
]
|
||||
boolean_options = []
|
||||
|
||||
def __init__(self, dist, **kw):
|
||||
self.babel_init_messages = babel.init_catalog(dist)
|
||||
Command.__init__(self, dist, **kw)
|
||||
|
||||
def initialize_options(self):
|
||||
self.locale = None
|
||||
self.babel_init_messages.initialize_options()
|
||||
|
||||
def finalize_options(self):
|
||||
self.babel_init_messages.locale = self.locale
|
||||
self.babel_init_messages.input_file = I18N_POT_FILE
|
||||
self.babel_init_messages.output_dir = I18N_OUTPUT_DIR_PY
|
||||
self.babel_init_messages.finalize_options()
|
||||
|
||||
def run(self):
|
||||
self.babel_init_messages.run()
|
||||
|
||||
|
||||
class RefreshTranslation(Command):
|
||||
description = "refresh translations"
|
||||
user_options = []
|
||||
boolean_options = []
|
||||
|
||||
def __init__(self, dist, **kw):
|
||||
self.babel_extract_messages = babel.extract_messages(dist)
|
||||
self.babel_update_messages = babel.update_catalog(dist)
|
||||
Command.__init__(self, dist, **kw)
|
||||
|
||||
def initialize_options(self):
|
||||
self.babel_extract_messages.initialize_options()
|
||||
self.babel_update_messages.initialize_options()
|
||||
|
||||
def finalize_options(self):
|
||||
self.babel_extract_messages.mapping_file = I18N_MAPPING_FILE
|
||||
self.babel_extract_messages.output_file = I18N_POT_FILE
|
||||
self.babel_extract_messages.input_dirs = I18N_INPUT_DIRS
|
||||
self.babel_extract_messages.msgid_bugs_address = "i18n@octoprint.org"
|
||||
self.babel_extract_messages.copyright_holder = "The OctoPrint Project"
|
||||
self.babel_extract_messages.finalize_options()
|
||||
|
||||
self.babel_update_messages.input_file = I18N_MAPPING_FILE
|
||||
self.babel_update_messages.output_dir = I18N_OUTPUT_DIR_PY
|
||||
|
||||
def run(self):
|
||||
self.babel_extract_messages.run()
|
||||
self.babel_update_messages.run()
|
||||
|
||||
|
||||
class CompileTranslation(Command):
|
||||
description = "compile translations"
|
||||
user_options = []
|
||||
boolean_options = []
|
||||
|
||||
def __init__(self, dist, **kw):
|
||||
self.babel_compile_messages = babel.compile_catalog(dist)
|
||||
Command.__init__(self, dist, **kw)
|
||||
|
||||
def initialize_options(self):
|
||||
self.babel_compile_messages.initialize_options()
|
||||
|
||||
def finalize_options(self):
|
||||
self.babel_compile_messages.directory = I18N_OUTPUT_DIR_PY
|
||||
|
||||
def run(self):
|
||||
self.babel_compile_messages.run()
|
||||
|
||||
for lang_code in os.listdir(I18N_OUTPUT_DIR_PY):
|
||||
full_path = os.path.join(I18N_OUTPUT_DIR_PY, lang_code)
|
||||
|
||||
if os.path.isdir(full_path):
|
||||
client_po_dir = os.path.join(full_path, "LC_MESSAGES")
|
||||
|
||||
po2json.update_js_file(
|
||||
"%s/%s.po" % (client_po_dir, I18N_DOMAIN),
|
||||
lang_code,
|
||||
I18N_OUTPUT_DIR_JS,
|
||||
I18N_DOMAIN
|
||||
)
|
||||
|
||||
|
||||
def get_cmdclass():
|
||||
cmdclass = versioneer.get_cmdclass()
|
||||
cmdclass.update({
|
||||
'clean': CleanCommand
|
||||
'clean': CleanCommand,
|
||||
'babel_new': NewTranslation,
|
||||
'babel_refresh': RefreshTranslation,
|
||||
'babel_compile': CompileTranslation
|
||||
})
|
||||
return cmdclass
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms
|
|||
|
||||
import uuid
|
||||
from sockjs.tornado import SockJSRouter
|
||||
from flask import Flask, render_template, send_from_directory
|
||||
from flask import Flask, render_template, send_from_directory, g, request
|
||||
from flask.ext.login import LoginManager
|
||||
from flask.ext.principal import Principal, Permission, RoleNeed, identity_loaded, UserNeed
|
||||
from flask.ext.babel import Babel
|
||||
from babel import Locale
|
||||
from watchdog.observers import Observer
|
||||
|
||||
import os
|
||||
|
|
@ -20,6 +22,7 @@ SUCCESS = {}
|
|||
NO_CONTENT = ("", 204)
|
||||
|
||||
app = Flask("octoprint")
|
||||
babel = Babel(app)
|
||||
debug = False
|
||||
|
||||
printer = None
|
||||
|
|
@ -49,6 +52,35 @@ UI_API_KEY = ''.join('%02X' % ord(z) for z in uuid.uuid4().bytes)
|
|||
VERSION = octoprint._version.get_versions()['version']
|
||||
|
||||
|
||||
def get_available_locale_identifiers(locales):
|
||||
result = set()
|
||||
|
||||
# add available translations
|
||||
for locale in locales:
|
||||
result.add(locale.language)
|
||||
if locale.territory:
|
||||
# if a territory is specified, add that too
|
||||
result.add("%s_%s" % (locale.language, locale.territory))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
LOCALES = [Locale.parse("en")] + babel.list_translations()
|
||||
LANGUAGES = get_available_locale_identifiers(LOCALES)
|
||||
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
g.locale = get_locale()
|
||||
|
||||
|
||||
@babel.localeselector
|
||||
def get_locale():
|
||||
if "l10n" in request.values:
|
||||
return Locale.negotiate([request.values["l10n"]], LANGUAGES)
|
||||
return request.accept_languages.best_match(LANGUAGES)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return render_template(
|
||||
|
|
|
|||
|
|
@ -40,11 +40,7 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM
|
|||
};
|
||||
|
||||
self._onclose = function() {
|
||||
$("#offline_overlay_message").html(
|
||||
"The server appears to be offline, at least I'm not getting any response from it. I'll try to reconnect " +
|
||||
"automatically <strong>over the next couple of minutes</strong>, however you are welcome to try a manual reconnect " +
|
||||
"anytime using the button below."
|
||||
);
|
||||
$("#offline_overlay_message").html(gettext("The server appears to be offline, at least I'm not getting any response from it. I'll try to reconnect automatically <strong>over the next couple of minutes</strong>, however you are welcome to try a manual reconnect anytime using the button below."));
|
||||
if (!$("#offline_overlay").is(":visible"))
|
||||
$("#offline_overlay").show();
|
||||
|
||||
|
|
@ -59,10 +55,7 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM
|
|||
};
|
||||
|
||||
self._onreconnectfailed = function() {
|
||||
$("#offline_overlay_message").html(
|
||||
"The server appears to be offline, at least I'm not getting any response from it. I <strong>could not reconnect automatically</strong>, " +
|
||||
"but you may try a manual reconnect using the button below."
|
||||
);
|
||||
$("#offline_overlay_message").html(gettext("The server appears to be offline, at least I'm not getting any response from it. I <strong>could not reconnect automatically</strong>, but you may try a manual reconnect using the button below."));
|
||||
};
|
||||
|
||||
self._onmessage = function(e) {
|
||||
|
|
@ -129,40 +122,40 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM
|
|||
if ((type == "UpdatedFiles" && payload.type == "gcode") || type == "MetadataAnalysisFinished") {
|
||||
gcodeFilesViewModel.requestData();
|
||||
} else if (type == "MovieRendering") {
|
||||
new PNotify({title: "Rendering timelapse", text: "Now rendering timelapse " + payload.movie_basename});
|
||||
new PNotify({title: gettext("Rendering timelapse"), text: _.sprintf(gettext("Now rendering timelapse %(movie_basename)s"), payload)});
|
||||
} else if (type == "MovieDone") {
|
||||
new PNotify({title: "Timelapse ready", text: "New timelapse " + payload.movie_basename + " is done rendering."});
|
||||
new PNotify({title: gettext("Timelapse ready"), text: _.sprintf(gettext("New timelapse %(movie_basename)s is done rendering."), payload)});
|
||||
timelapseViewModel.requestData();
|
||||
} else if (type == "MovieFailed") {
|
||||
html = "<p>Rendering of timelapse " + payload.movie_basename + " failed with return code " + payload.returncode + "</p>";
|
||||
html = "<p>" + _.sprintf(gettext("Rendering of timelapse %(movie_basename)s failedwith return code %(returncode)s"), payload) + "</p>";
|
||||
html += pnotifyAdditionalInfo('<pre style="overflow: auto">' + payload.error + '</pre>');
|
||||
new PNotify({title: "Rendering failed", text: html, type: "error", hide: false});
|
||||
new PNotify({title: gettext("Rendering failed"), text: html, type: "error", hide: false});
|
||||
} else if (type == "SlicingStarted") {
|
||||
gcodeUploadProgress.addClass("progress-striped").addClass("active");
|
||||
gcodeUploadProgressBar.css("width", "100%");
|
||||
gcodeUploadProgressBar.text("Slicing ...");
|
||||
gcodeUploadProgressBar.text(gettext("Slicing ..."));
|
||||
} else if (type == "SlicingDone") {
|
||||
gcodeUploadProgress.removeClass("progress-striped").removeClass("active");
|
||||
gcodeUploadProgressBar.css("width", "0%");
|
||||
gcodeUploadProgressBar.text("");
|
||||
new PNotify({title: "Slicing done", text: "Sliced " + payload.stl + " to " + payload.gcode + ", took " + _.sprintf("%.2f", payload.time) + " seconds"});
|
||||
new PNotify({title: gettext("Slicing done"), text: _.sprintf(gettext("Sliced %(stl)s to %(gcode)s, took %(time).2f seconds"), payload)});
|
||||
gcodeFilesViewModel.requestData(payload.gcode);
|
||||
} else if (type == "SlicingFailed") {
|
||||
gcodeUploadProgress.removeClass("progress-striped").removeClass("active");
|
||||
gcodeUploadProgressBar.css("width", "0%");
|
||||
gcodeUploadProgressBar.text("");
|
||||
|
||||
html = "Could not slice " + payload.stl + " to " + payload.gcode + ": " + payload.reason;
|
||||
new PNotify({title: "Slicing failed", text: html, type: "error", hide: false});
|
||||
html = _.sprintf(gettext("Could not slice %(stl)s to %(gcode)s: %(reason)s"), payload);
|
||||
new PNotify({title: gettext("Slicing failed"), text: html, type: "error", hide: false});
|
||||
} else if (type == "TransferStarted") {
|
||||
gcodeUploadProgress.addClass("progress-striped").addClass("active");
|
||||
gcodeUploadProgressBar.css("width", "100%");
|
||||
gcodeUploadProgressBar.text("Streaming ...");
|
||||
gcodeUploadProgressBar.text(gettext("Streaming ..."));
|
||||
} else if (type == "TransferDone") {
|
||||
gcodeUploadProgress.removeClass("progress-striped").removeClass("active");
|
||||
gcodeUploadProgressBar.css("width", "0%");
|
||||
gcodeUploadProgressBar.text("");
|
||||
new PNotify({title: "Streaming done", text: "Streamed " + payload.local + " to " + payload.remote + " on SD, took " + _.sprintf("%.2f", payload.time) + " seconds"});
|
||||
new PNotify({title: gettext("Streaming done"), text: _.sprintf(gettext("Streamed %(local)s to %(remote)s on SD, took %(time).2f seconds"), payload)});
|
||||
gcodeFilesViewModel.requestData(payload.remote, "sdcard");
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSor
|
|||
self.searchFunction = undefined;
|
||||
|
||||
self.allItems = [];
|
||||
|
||||
0
|
||||
self.items = ko.observableArray([]);
|
||||
self.pageSize = ko.observable(filesPerPage);
|
||||
self.currentPage = ko.observable(0);
|
||||
|
|
@ -321,12 +321,12 @@ function formatDuration(seconds) {
|
|||
var m = (seconds % 3600) / 60;
|
||||
var h = seconds / 3600;
|
||||
|
||||
return _.sprintf("%02d:%02d:%02d", h, m, s);
|
||||
return _.sprintf(gettext(/* L10N: duration format */ "%(hour)02d:%(minute)02d:%(second)02d"), {hour: h, minute: m, second: s});
|
||||
}
|
||||
|
||||
function formatDate(unixTimestamp) {
|
||||
if (!unixTimestamp) return "-";
|
||||
return moment.unix(unixTimestamp).format("YYYY-MM-DD HH:mm");
|
||||
return moment.unix(unixTimestamp).format(gettext(/* L10N: Date format */ "YYYY-MM-DD HH:mm"));
|
||||
}
|
||||
|
||||
function formatTimeAgo(unixTimestamp) {
|
||||
|
|
@ -336,20 +336,20 @@ function formatTimeAgo(unixTimestamp) {
|
|||
|
||||
function formatFilament(filament) {
|
||||
if (!filament || !filament["length"]) return "-";
|
||||
var result = _.sprintf("%.02fm", (filament["length"] / 1000));
|
||||
var result = "%(length).02fm";
|
||||
if (filament.hasOwnProperty("volume") && filament.volume) {
|
||||
result += " / " + _.sprintf("%.02fcm³", filament["volume"]);
|
||||
result += " / " + "%(volume).02fcm³";
|
||||
}
|
||||
return result;
|
||||
return _.sprintf(result, {length: filament["length"] / 1000, volume: filament["volume"]});
|
||||
}
|
||||
|
||||
function cleanTemperature(temp) {
|
||||
if (!temp || temp < 10) return "off";
|
||||
if (!temp || temp < 10) return gettext("off");
|
||||
return temp;
|
||||
}
|
||||
|
||||
function formatTemperature(temp) {
|
||||
if (!temp || temp < 10) return "off";
|
||||
if (!temp || temp < 10) return gettext("off");
|
||||
return _.sprintf("%.1f°C", temp);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,30 @@
|
|||
$(function() {
|
||||
//~~ Initialize i18n
|
||||
var catalog = window["BABEL_TO_LOAD_" + LOCALE];
|
||||
if (catalog === undefined) {
|
||||
catalog = {messages: undefined, plural_expr: undefined, locale: undefined, domain: undefined}
|
||||
}
|
||||
babel.Translations.load(catalog).install();
|
||||
|
||||
moment.locale(LOCALE);
|
||||
|
||||
// Dummy translation requests for dynamic strings supplied by the backend
|
||||
var dummyTranslations = [
|
||||
// printer states
|
||||
gettext("Offline"),
|
||||
gettext("Opening serial port"),
|
||||
gettext("Detecting serial port"),
|
||||
gettext("Detecting baudrate"),
|
||||
gettext("Connecting"),
|
||||
gettext("Operational"),
|
||||
gettext("Printing from SD"),
|
||||
gettext("Sending file to SD"),
|
||||
gettext("Printing"),
|
||||
gettext("Paused"),
|
||||
gettext("Closed"),
|
||||
gettext("Transfering file to SD")
|
||||
];
|
||||
|
||||
//~~ Initialize view models
|
||||
var loginStateViewModel = new LoginStateViewModel();
|
||||
var usersViewModel = new UsersViewModel(loginStateViewModel);
|
||||
|
|
@ -92,7 +118,7 @@ $(function() {
|
|||
}
|
||||
|
||||
function gcode_upload_fail(e, data) {
|
||||
var error = "<p>Could not upload the file. Make sure that it is a GCODE file and has the extension \".gcode\" or \".gco\" or that it is an STL file with the extension \".stl\" and slicing support is enabled and configured.</p>";
|
||||
var error = "<p>" + gettext("Could not upload the file. Make sure that it is a GCODE file and has the extension \".gcode\" or \".gco\" or that it is an STL file with the extension \".stl\" and slicing support is enabled and configured.") + "</p>";
|
||||
error += pnotifyAdditionalInfo("<pre>" + data.jqXHR.responseText + "</pre>");
|
||||
new PNotify({
|
||||
title: "Upload failed",
|
||||
|
|
@ -108,10 +134,10 @@ $(function() {
|
|||
function gcode_upload_progress(e, data) {
|
||||
var progress = parseInt(data.loaded / data.total * 100, 10);
|
||||
$("#gcode_upload_progress .bar").css("width", progress + "%");
|
||||
$("#gcode_upload_progress .bar").text("Uploading ...");
|
||||
$("#gcode_upload_progress .bar").text(gettext("Uploading ..."));
|
||||
if (progress >= 100) {
|
||||
$("#gcode_upload_progress").addClass("progress-striped").addClass("active");
|
||||
$("#gcode_upload_progress .bar").text("Saving ...");
|
||||
$("#gcode_upload_progress .bar").text(gettext("Saving ..."));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ function AppearanceViewModel(settingsViewModel) {
|
|||
return "OctoPrint: " + self.name();
|
||||
else
|
||||
return "OctoPrint";
|
||||
})
|
||||
});
|
||||
|
||||
self.title = ko.computed(function() {
|
||||
if (self.name())
|
||||
return self.name() + " [OctoPrint]";
|
||||
else
|
||||
return "OctoPrint";
|
||||
})
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
|
|||
|
||||
self.buttonText = ko.computed(function() {
|
||||
if (self.isErrorOrClosed())
|
||||
return "Connect";
|
||||
return gettext("Connect");
|
||||
else
|
||||
return "Disconnect";
|
||||
return gettext("Disconnect");
|
||||
})
|
||||
|
||||
self.previousIsOperational = undefined;
|
||||
|
|
|
|||
|
|
@ -34,13 +34,13 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
// multiple extruders
|
||||
for (var extruder = 0; extruder < numExtruders; extruder++) {
|
||||
tools[extruder] = self._createToolEntry();
|
||||
tools[extruder]["name"]("Tool " + extruder);
|
||||
tools[extruder]["name"](gettext("Tool") + " " + extruder);
|
||||
tools[extruder]["key"]("tool" + extruder);
|
||||
}
|
||||
} else {
|
||||
// only one extruder, no need to add numbers
|
||||
tools[0] = self._createToolEntry();
|
||||
tools[0]["name"]("Hotend");
|
||||
tools[0]["name"](gettext("Hotend"));
|
||||
tools[0]["key"]("tool0");
|
||||
}
|
||||
|
||||
|
|
@ -49,11 +49,11 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
|
||||
self.fromCurrentData = function(data) {
|
||||
self._processStateData(data.state);
|
||||
}
|
||||
};
|
||||
|
||||
self.fromHistoryData = function(data) {
|
||||
self._processStateData(data.state);
|
||||
}
|
||||
};
|
||||
|
||||
self._processStateData = function(data) {
|
||||
self.isErrorOrClosed(data.flags.closedOrError);
|
||||
|
|
@ -63,13 +63,13 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
self.isError(data.flags.error);
|
||||
self.isReady(data.flags.ready);
|
||||
self.isLoading(data.flags.loading);
|
||||
}
|
||||
};
|
||||
|
||||
self.fromFeedbackCommandData = function(data) {
|
||||
if (data.name in self.feedbackControlLookup) {
|
||||
self.feedbackControlLookup[data.name](data.output);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.requestData = function() {
|
||||
$.ajax({
|
||||
|
|
@ -80,18 +80,18 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
self._fromResponse(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
self._fromResponse = function(response) {
|
||||
self.controls(self._processControls(response.controls));
|
||||
}
|
||||
};
|
||||
|
||||
self._processControls = function(controls) {
|
||||
for (var i = 0; i < controls.length; i++) {
|
||||
controls[i] = self._processControl(controls[i]);
|
||||
}
|
||||
return controls;
|
||||
}
|
||||
};
|
||||
|
||||
self._processControl = function(control) {
|
||||
if (control.type == "parametric_command" || control.type == "parametric_commands") {
|
||||
|
|
@ -105,7 +105,7 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
control.children = self._processControls(control.children);
|
||||
}
|
||||
return control;
|
||||
}
|
||||
};
|
||||
|
||||
self.sendJogCommand = function(axis, multiplier, distance) {
|
||||
if (typeof distance === "undefined")
|
||||
|
|
@ -116,7 +116,7 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
|
||||
var data = {
|
||||
"command": "jog"
|
||||
}
|
||||
};
|
||||
data[axis] = distance * multiplier;
|
||||
|
||||
$.ajax({
|
||||
|
|
@ -126,13 +126,13 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
contentType: "application/json; charset=UTF-8",
|
||||
data: JSON.stringify(data)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
self.sendHomeCommand = function(axis) {
|
||||
var data = {
|
||||
"command": "home",
|
||||
"axes": axis
|
||||
}
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: API_BASEURL + "printer/printhead",
|
||||
|
|
@ -141,7 +141,7 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
contentType: "application/json; charset=UTF-8",
|
||||
data: JSON.stringify(data)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
self.sendExtrudeCommand = function() {
|
||||
self._sendECommand(1);
|
||||
|
|
@ -217,7 +217,7 @@ function ControlViewModel(loginStateViewModel, settingsViewModel) {
|
|||
contentType: "application/json; charset=UTF-8",
|
||||
data: JSON.stringify(data)
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
self.displayMode = function(customControl) {
|
||||
switch (customControl.type) {
|
||||
|
|
|
|||
|
|
@ -273,21 +273,21 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel) {
|
|||
if (data["gcodeAnalysis"]["filament"] && typeof(data["gcodeAnalysis"]["filament"]) == "object") {
|
||||
var filament = data["gcodeAnalysis"]["filament"];
|
||||
if (_.keys(filament).length == 1) {
|
||||
output += "Filament: " + formatFilament(data["gcodeAnalysis"]["filament"]["tool" + 0]) + "<br>";
|
||||
output += gettext("Filament") + ": " + formatFilament(data["gcodeAnalysis"]["filament"]["tool" + 0]) + "<br>";
|
||||
} else if (_.keys(filament).length > 1) {
|
||||
for (var toolKey in filament) {
|
||||
if (!_.startsWith(toolKey, "tool") || !filament[toolKey] || !filament[toolKey].hasOwnProperty("length") || filament[toolKey]["length"] <= 0) continue;
|
||||
|
||||
output += "Filament (Tool " + toolKey.substr("tool".length) + "): " + formatFilament(filament[toolKey]) + "<br>";
|
||||
output += gettext("Filament") + " (" + gettext("Tool") + " " + toolKey.substr("tool".length) + "): " + formatFilament(filament[toolKey]) + "<br>";
|
||||
}
|
||||
}
|
||||
}
|
||||
output += "Estimated Print Time: " + formatDuration(data["gcodeAnalysis"]["estimatedPrintTime"]) + "<br>";
|
||||
output += gettext("Estimated Print Time") + ": " + formatDuration(data["gcodeAnalysis"]["estimatedPrintTime"]) + "<br>";
|
||||
}
|
||||
if (data["prints"] && data["prints"]["last"]) {
|
||||
output += "Last Printed: " + formatTimeAgo(data["prints"]["last"]["date"]) + "<br>";
|
||||
output += gettext("Last Printed") + ": " + formatTimeAgo(data["prints"]["last"]["date"]) + "<br>";
|
||||
if (data["prints"]["last"]["lastPrintTime"]) {
|
||||
output += "Last Print Time: " + formatDuration(data["prints"]["last"]["lastPrintTime"]);
|
||||
output += gettext("Last Print Time") + ": " + formatDuration(data["prints"]["last"]["lastPrintTime"]);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
|
|
|
|||
|
|
@ -34,9 +34,7 @@ function FirstRunViewModel() {
|
|||
};
|
||||
|
||||
self.disableAccessControl = function() {
|
||||
$("#confirmation_dialog .confirmation_dialog_message").html("If you disable Access Control <strong>and</strong> your OctoPrint " +
|
||||
"installation is accessible from the internet, your printer <strong>will be accessible by everyone - " +
|
||||
"that also includes the bad guys!</strong>");
|
||||
$("#confirmation_dialog .confirmation_dialog_message").html(gettext("If you disable Access Control <strong>and</strong> your OctoPrint installation is accessible from the internet, your printer <strong>will be accessible by everyone - that also includes the bad guys!</strong>"));
|
||||
$("#confirmation_dialog .confirmation_dialog_acknowledge").unbind("click");
|
||||
$("#confirmation_dialog .confirmation_dialog_acknowledge").click(function(e) {
|
||||
e.preventDefault();
|
||||
|
|
|
|||
|
|
@ -10,15 +10,15 @@ function GcodeViewModel(loginStateViewModel, settingsViewModel) {
|
|||
var text = "";
|
||||
switch (self.ui_progress_type()) {
|
||||
case "loading": {
|
||||
text = "Loading... (" + self.ui_progress_percentage().toFixed(0) + "%)";
|
||||
text = gettext("Loading...") + " (" + self.ui_progress_percentage().toFixed(0) + "%)";
|
||||
break;
|
||||
}
|
||||
case "analyzing": {
|
||||
text = "Analyzing... (" + self.ui_progress_percentage().toFixed(0) + "%)";
|
||||
text = gettext("Analyzing...") + " (" + self.ui_progress_percentage().toFixed(0) + "%)";
|
||||
break;
|
||||
}
|
||||
case "done": {
|
||||
text = "Analyzed";
|
||||
text = gettext("Analyzed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -322,17 +322,17 @@ function GcodeViewModel(loginStateViewModel, settingsViewModel) {
|
|||
self.currentLayer = 0;
|
||||
} else {
|
||||
var output = [];
|
||||
output.push("Model size is: " + model.width.toFixed(2) + "mm × " + model.depth.toFixed(2) + "mm × " + model.height.toFixed(2) + "mm");
|
||||
output.push(gettext("Model size") + ": " + model.width.toFixed(2) + "mm × " + model.depth.toFixed(2) + "mm × " + model.height.toFixed(2) + "mm");
|
||||
if (model.filament.length == 0) {
|
||||
output.push("Total filament used: " + model.filament.toFixed(2) + "mm");
|
||||
output.push(gettext("Total filament used") + ": " + model.filament.toFixed(2) + gettext("mm"));
|
||||
} else {
|
||||
for (var i = 0; i < model.filament.length; i++) {
|
||||
output.push("Total filament used (Tool " + i + "): " + model.filament[i].toFixed(2) + "mm");
|
||||
output.push(gettext("Total filament used") + " (" + gettext("Tool") + " " + i + "): " + model.filament[i].toFixed(2) + gettext("mm"));
|
||||
}
|
||||
}
|
||||
output.push("Estimated print time: " + formatDuration(model.printTime));
|
||||
output.push("Estimated layer height: " + model.layerHeight.toFixed(2) + "mm");
|
||||
output.push("Layer count: " + model.layersPrinted.toFixed(0) + " printed, " + model.layersTotal.toFixed(0) + " visited");
|
||||
output.push(gettext("Estimated print time") + ": " + formatDuration(model.printTime));
|
||||
output.push(gettext("Estimated layer height") + ": " + model.layerHeight.toFixed(2) + gettext("mm"));
|
||||
output.push(gettext("Layer count") + ": " + model.layersPrinted.toFixed(0) + " " + gettext(printed) + ", " + model.layersTotal.toFixed(0) + " " + gettext("visited"));
|
||||
|
||||
self.ui_modelInfo(output.join("<br>"));
|
||||
|
||||
|
|
@ -351,17 +351,17 @@ function GcodeViewModel(loginStateViewModel, settingsViewModel) {
|
|||
self.currentCommand = [0, 1];
|
||||
} else {
|
||||
var output = [];
|
||||
output.push("Layer number: " + (layer.number + 1));
|
||||
output.push("Layer height (mm): " + layer.height);
|
||||
output.push("GCODE commands in layer: " + layer.commands);
|
||||
output.push(gettext("Layer number") + ": " + (layer.number + 1));
|
||||
output.push(gettext("Layer height") + " (mm): " + layer.height);
|
||||
output.push(gettext("GCODE commands in layer") + ": " + layer.commands);
|
||||
if (layer.filament.length == 1) {
|
||||
output.push("Filament used by layer: " + layer.filament[0].toFixed(2) + "mm");
|
||||
output.push(gettext("Filament used by layer") + ": " + layer.filament[0].toFixed(2) + "mm");
|
||||
} else {
|
||||
for (var i = 0; i < layer.filament.length; i++) {
|
||||
output.push("Filament used by layer (Tool " + i + "): " + layer.filament[i].toFixed(2) + "mm");
|
||||
output.push(gettext("Filament used by layer") + " (" + gettext("Tool") + " " + i + "): " + layer.filament[i].toFixed(2) + "mm");
|
||||
}
|
||||
}
|
||||
output.push("Print time for layer: " + formatDuration(layer.printTime));
|
||||
output.push(gettext("Print time for layer") + ": " + formatDuration(layer.printTime));
|
||||
|
||||
self.ui_layerInfo(output.join("<br>"));
|
||||
|
||||
|
|
|
|||
|
|
@ -12,15 +12,15 @@ function LoginStateViewModel() {
|
|||
if (self.loggedIn()) {
|
||||
return "\"" + self.username() + "\"";
|
||||
} else {
|
||||
return "Login";
|
||||
return gettext("Login");
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
self.subscribers = [];
|
||||
self.subscribe = function(callback) {
|
||||
if (callback === undefined) return;
|
||||
self.subscribers.push(callback);
|
||||
}
|
||||
};
|
||||
|
||||
self.requestData = function() {
|
||||
$.ajax({
|
||||
|
|
@ -51,7 +51,7 @@ function LoginStateViewModel() {
|
|||
|
||||
_.each(self.subscribers, function(callback) { callback("logout", {}); });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.login = function() {
|
||||
var username = $("#login_user").val();
|
||||
|
|
@ -67,21 +67,21 @@ function LoginStateViewModel() {
|
|||
type: "POST",
|
||||
data: {"user": username, "pass": password, "remember": remember},
|
||||
success: function(response) {
|
||||
new PNotify({title: "Login successful", text: "You are now logged in as \"" + response.name + "\"", type: "success"});
|
||||
new PNotify({title: gettext("Login successful"), text: _.sprintf(gettext('You are now logged in as "%(username)s"'), {username: response.name}), type: "success"});
|
||||
self.fromResponse(response);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
new PNotify({title: "Login failed", text: "User unknown or wrong password", type: "error"});
|
||||
new PNotify({title: gettext("Login failed"), text: gettext("User unknown or wrong password"), type: "error"});
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
self.logout = function() {
|
||||
$.ajax({
|
||||
url: API_BASEURL + "logout",
|
||||
type: "POST",
|
||||
success: function(response) {
|
||||
new PNotify({title: "Logout successful", text: "You are now logged out", type: "success"});
|
||||
new PNotify({title: gettext("Logout successful"), text: gettext("You are now logged out"), type: "success"});
|
||||
self.fromResponse(response);
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ function NavigationViewModel(loginStateViewModel, appearanceViewModel, settingsV
|
|||
dataType: "json",
|
||||
data: "action=" + action.action,
|
||||
success: function() {
|
||||
new PNotify({title: "Success", text: "The command \""+ action.name +"\" executed successfully", type: "success"});
|
||||
new PNotify({title: "Success", text: _.sprintf(gettext("The command \"%(command)s\" executed successfully"), {command: action.name}), type: "success"});
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
var error = "<p>The command \"" + action.name + "\" could not be executed.</p>";
|
||||
var error = "<p>" + _.sprintf(gettext("The command \"%(command)s\" could not be executed."), {command: action.name}) + "</p>";
|
||||
error += pnotifyAdditionalInfo("<pre>" + jqXHR.responseText + "</pre>");
|
||||
new PNotify({title: "Error", text: error, type: "error", hide: false});
|
||||
new PNotify({title: gettext("Error"), text: error, type: "error", hide: false});
|
||||
}
|
||||
})
|
||||
};
|
||||
|
|
|
|||
|
|
@ -63,9 +63,9 @@ function PrinterStateViewModel(loginStateViewModel) {
|
|||
});
|
||||
self.pauseString = ko.computed(function() {
|
||||
if (self.isPaused())
|
||||
return "Continue";
|
||||
return gettext("Continue");
|
||||
else
|
||||
return "Pause";
|
||||
return gettext("Pause");
|
||||
});
|
||||
|
||||
self.timelapseString = ko.computed(function() {
|
||||
|
|
@ -76,9 +76,9 @@ function PrinterStateViewModel(loginStateViewModel) {
|
|||
|
||||
var type = timelapse["type"];
|
||||
if (type == "zchange") {
|
||||
return "On Z Change";
|
||||
return gettext("On Z Change");
|
||||
} else if (type == "timed") {
|
||||
return "Timed (" + timelapse["options"]["interval"] + "s)";
|
||||
return gettext("Timed") + " (" + timelapse["options"]["interval"] + " " + gettext("sec") + ")";
|
||||
} else {
|
||||
return "-";
|
||||
}
|
||||
|
|
@ -104,7 +104,7 @@ function PrinterStateViewModel(loginStateViewModel) {
|
|||
};
|
||||
|
||||
self._processStateData = function(data) {
|
||||
self.stateString(data.text);
|
||||
self.stateString(gettext(data.text));
|
||||
self.isErrorOrClosed(data.flags.closedOrError);
|
||||
self.isOperational(data.flags.operational);
|
||||
self.isPaused(data.flags.paused);
|
||||
|
|
@ -134,7 +134,7 @@ function PrinterStateViewModel(loginStateViewModel) {
|
|||
if (!_.startsWith(key, "tool") || !data.filament[key] || !data.filament[key].hasOwnProperty("length") || data.filament[key].length <= 0) continue;
|
||||
|
||||
result.push({
|
||||
name: ko.observable("Tool " + key.substr("tool".length)),
|
||||
name: ko.observable(gettext("Tool") + " " + key.substr("tool".length)),
|
||||
data: ko.observable(data.filament[key])
|
||||
});
|
||||
}
|
||||
|
|
@ -163,7 +163,7 @@ function PrinterStateViewModel(loginStateViewModel) {
|
|||
};
|
||||
|
||||
if (self.isPaused()) {
|
||||
$("#confirmation_dialog .confirmation_dialog_message").text("This will restart the print job from the beginning.");
|
||||
$("#confirmation_dialog .confirmation_dialog_message").text(gettext("This will restart the print job from the beginning."));
|
||||
$("#confirmation_dialog .confirmation_dialog_acknowledge").unbind("click");
|
||||
$("#confirmation_dialog .confirmation_dialog_acknowledge").click(function(e) {e.preventDefault(); $("#confirmation_dialog").modal("hide"); restartCommand(); });
|
||||
$("#confirmation_dialog").modal("show");
|
||||
|
|
|
|||
|
|
@ -11,7 +11,38 @@ function SettingsViewModel(loginStateViewModel, usersViewModel) {
|
|||
self.appearance_name = ko.observable(undefined);
|
||||
self.appearance_color = ko.observable(undefined);
|
||||
|
||||
self.appearance_available_colors = ko.observable(["default", "red", "orange", "yellow", "green", "blue", "violet", "black"]);
|
||||
self.appearance_available_colors = ko.observable([
|
||||
{key: "default", name: gettext("default")},
|
||||
{key: "red", name: gettext("red")},
|
||||
{key: "orange", name: gettext("orange")},
|
||||
{key: "yellow", name: gettext("yellow")},
|
||||
{key: "green", name: gettext("green")},
|
||||
{key: "blue", name: gettext("blue")},
|
||||
{key: "violet", name: gettext("violet")},
|
||||
{key: "black", name: gettext("black")},
|
||||
]);
|
||||
self.appearance_colorName = function(color) {
|
||||
switch (color) {
|
||||
case "red":
|
||||
return gettext("red");
|
||||
case "orange":
|
||||
return gettext("orange");
|
||||
case "yellow":
|
||||
return gettext("yellow");
|
||||
case "green":
|
||||
return gettext("green");
|
||||
case "blue":
|
||||
return gettext("blue");
|
||||
case "violet":
|
||||
return gettext("violet");
|
||||
case "black":
|
||||
return gettext("black");
|
||||
case "default":
|
||||
return gettext("default");
|
||||
default:
|
||||
return color;
|
||||
}
|
||||
};
|
||||
|
||||
self.printer_movementSpeedX = ko.observable(undefined);
|
||||
self.printer_movementSpeedY = ko.observable(undefined);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
|
|||
self.tools = ko.observableArray([]);
|
||||
self.hasBed = ko.observable(true);
|
||||
self.bedTemp = self._createToolEntry();
|
||||
self.bedTemp["name"]("Bed");
|
||||
self.bedTemp["name"](gettext("Bed"));
|
||||
self.bedTemp["key"]("bed");
|
||||
|
||||
self.isErrorOrClosed = ko.observable(undefined);
|
||||
|
|
@ -51,7 +51,7 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
|
|||
if (tools.length <= extruder || !tools[extruder]) {
|
||||
tools[extruder] = self._createToolEntry();
|
||||
}
|
||||
tools[extruder]["name"]("Tool " + extruder);
|
||||
tools[extruder]["name"](gettext("Tool") + " " + extruder);
|
||||
tools[extruder]["key"]("tool" + extruder);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -62,12 +62,12 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
|
|||
if (tools.length < 1 || !tools[0]) {
|
||||
tools[0] = self._createToolEntry();
|
||||
}
|
||||
tools[0]["name"]("Hotend");
|
||||
tools[0]["name"](gettext("Hotend"));
|
||||
tools[0]["key"]("tool0");
|
||||
}
|
||||
|
||||
// print bed
|
||||
heaterOptions["bed"] = {name: "Bed", color: "blue"};
|
||||
heaterOptions["bed"] = {name: gettext("Bed"), color: "blue"};
|
||||
|
||||
// write back
|
||||
self.heaterOptions(heaterOptions);
|
||||
|
|
@ -98,9 +98,9 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
|
|||
// convert to minutes
|
||||
var diffInMins = Math.round(diff / (60 * 1000));
|
||||
if (diffInMins == 0)
|
||||
return "just now";
|
||||
return gettext("just now");
|
||||
else
|
||||
return "- " + diffInMins + " min";
|
||||
return "- " + diffInMins + " " + gettext("min");
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
|
|
@ -238,12 +238,12 @@ function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
|
|||
var targetTemp = targets && targets.length ? formatTemperature(targets[targets.length - 1][1]) : "-";
|
||||
|
||||
data.push({
|
||||
label: "Actual " + heaterOptions[type].name + ": " + actualTemp,
|
||||
label: gettext("Actual") + " " + heaterOptions[type].name + ": " + actualTemp,
|
||||
color: heaterOptions[type].color,
|
||||
data: actuals
|
||||
});
|
||||
data.push({
|
||||
label: "Target " + heaterOptions[type].name + ": " + targetTemp,
|
||||
label: gettext("Target") + " " + heaterOptions[type].name + ": " + targetTemp,
|
||||
color: pusher.color(heaterOptions[type].color).tint(0.5).html(),
|
||||
data: targets
|
||||
});
|
||||
|
|
|
|||
7
src/octoprint/static/js/i18n/README.txt
Normal file
7
src/octoprint/static/js/i18n/README.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
Note to developers
|
||||
------------------
|
||||
|
||||
Do not edit the files in this directory directly, they are auto generated from the i18n PO files. If you want to
|
||||
change any translations in here, please follow the translation guide on the Wiki:
|
||||
|
||||
https://github.com/foosel/OctoPrint/wiki/Translating-OctoPrint
|
||||
1
src/octoprint/static/js/i18n/de.js
Normal file
1
src/octoprint/static/js/i18n/de.js
Normal file
File diff suppressed because one or more lines are too long
160
src/octoprint/static/js/lib/babel.js
Normal file
160
src/octoprint/static/js/lib/babel.js
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/**
|
||||
* Babel JavaScript Support
|
||||
*
|
||||
* Copyright (C) 2008-2011 Edgewall Software
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution. The terms
|
||||
* are also available at http://babel.edgewall.org/wiki/License.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals. For the exact contribution history, see the revision
|
||||
* history and logs, available at http://babel.edgewall.org/log/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A simple module that provides a gettext like translation interface.
|
||||
* The catalog passed to load() must be a object conforming to this
|
||||
* interface::
|
||||
*
|
||||
* {
|
||||
* messages: an object of {msgid: translations} items where
|
||||
* translations is an array of messages or a single
|
||||
* string if the message is not pluralizable.
|
||||
* plural_expr: the plural expression for the language.
|
||||
* locale: the identifier for this locale.
|
||||
* domain: the name of the domain.
|
||||
* }
|
||||
*
|
||||
* Missing elements in the object are ignored.
|
||||
*
|
||||
* Typical usage::
|
||||
*
|
||||
* var translations = babel.Translations.load(...).install();
|
||||
*/
|
||||
var babel = new function() {
|
||||
|
||||
var defaultPluralExpr = function(n) { return n == 1 ? 0 : 1; };
|
||||
var formatRegex = /%?%(?:\(([^\)]+)\))?([disr])/g;
|
||||
|
||||
/**
|
||||
* A translations object implementing the gettext interface
|
||||
*/
|
||||
var Translations = this.Translations = function(locale, domain) {
|
||||
this.messages = {};
|
||||
this.locale = locale || 'unknown';
|
||||
this.domain = domain || 'messages';
|
||||
this.pluralexpr = defaultPluralExpr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new translations object from the catalog and return it.
|
||||
* See the babel-module comment for more details.
|
||||
*/
|
||||
Translations.load = function(catalog) {
|
||||
var rv = new Translations();
|
||||
rv.load(catalog);
|
||||
return rv;
|
||||
};
|
||||
|
||||
Translations.prototype = {
|
||||
/**
|
||||
* translate a single string.
|
||||
*/
|
||||
gettext: function(string) {
|
||||
var translated = this.messages[string];
|
||||
if (typeof translated == 'undefined')
|
||||
return string;
|
||||
return (typeof translated == 'string') ? translated : translated[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* translate a pluralizable string
|
||||
*/
|
||||
ngettext: function(singular, plural, n) {
|
||||
var translated = this.messages[singular];
|
||||
if (typeof translated == 'undefined')
|
||||
return (n == 1) ? singular : plural;
|
||||
return translated[this.pluralexpr(n)];
|
||||
},
|
||||
|
||||
/**
|
||||
* Install this translation document wide. After this call, there are
|
||||
* three new methods on the window object: _, gettext and ngettext
|
||||
*/
|
||||
install: function() {
|
||||
var self = this;
|
||||
window.gettext = function(string) {
|
||||
return self.gettext(string);
|
||||
};
|
||||
window.ngettext = function(singular, plural, n) {
|
||||
return self.ngettext(singular, plural, n);
|
||||
};
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Works like Translations.load but updates the instance rather
|
||||
* then creating a new one.
|
||||
*/
|
||||
load: function(catalog) {
|
||||
if (catalog.messages)
|
||||
this.update(catalog.messages);
|
||||
if (catalog.plural_expr)
|
||||
this.setPluralExpr(catalog.plural_expr);
|
||||
if (catalog.locale)
|
||||
this.locale = catalog.locale;
|
||||
if (catalog.domain)
|
||||
this.domain = catalog.domain;
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the translations with the object of messages.
|
||||
*/
|
||||
update: function(mapping) {
|
||||
for (var key in mapping)
|
||||
if (mapping.hasOwnProperty(key))
|
||||
this.messages[key] = mapping[key];
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the plural expression
|
||||
*/
|
||||
setPluralExpr: function(expr) {
|
||||
this.pluralexpr = new Function('n', 'return +(' + expr + ')');
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A python inspired string formatting function. Supports named and
|
||||
* positional placeholders and "s", "d" and "i" as type characters
|
||||
* without any formatting specifications.
|
||||
*
|
||||
* Examples::
|
||||
*
|
||||
* babel.format(_('Hello %s'), name)
|
||||
* babel.format(_('Progress: %(percent)s%%'), {percent: 100})
|
||||
*/
|
||||
this.format = function() {
|
||||
var arg, string = arguments[0], idx = 0;
|
||||
if (arguments.length == 1)
|
||||
return string;
|
||||
else if (arguments.length == 2 && typeof arguments[1] == 'object')
|
||||
arg = arguments[1];
|
||||
else {
|
||||
arg = [];
|
||||
for (var i = 1, n = arguments.length; i != n; ++i)
|
||||
arg[i - 1] = arguments[i];
|
||||
}
|
||||
return string.replace(formatRegex, function(all, name, type) {
|
||||
if (all[0] == all[1]) return all.substring(1);
|
||||
var value = arg[name || idx++];
|
||||
return (type == 'i' || type == 'd') ? +value : value;
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
10
src/octoprint/static/js/lib/moment-with-locales.min.js
vendored
Normal file
10
src/octoprint/static/js/lib/moment-with-locales.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
src/octoprint/static/js/lib/moment.min.js
vendored
6
src/octoprint/static/js/lib/moment.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -3,14 +3,10 @@
|
|||
<div id="offline_overlay_wrapper">
|
||||
<div class="container">
|
||||
<div class="hero-unit">
|
||||
<h1>Server is offline</h1>
|
||||
<p id="offline_overlay_message">
|
||||
The server appears to be offline, at least I'm not getting any response from it. I'll try to reconnect
|
||||
automatically <strong>over the next couple of minutes</strong>, however you are welcome to try a manual reconnect
|
||||
anytime using the button below.
|
||||
</p>
|
||||
<h1>{{ _('Server is offline') }}</h1>
|
||||
<p id="offline_overlay_message"></p>
|
||||
<p>
|
||||
<a class="btn btn-primary btn-large" id="offline_overlay_reconnect">Attempt to reconnect</a>
|
||||
<a class="btn btn-primary btn-large" id="offline_overlay_reconnect">{{ _('Attempt to reconnect') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -21,12 +17,12 @@
|
|||
<div id="drop_overlay_background"></div>
|
||||
<div id="drop_overlay_wrapper">
|
||||
{% if enableSdSupport %}
|
||||
<div class="dropzone" id="drop_locally"><span class="centered"><i class="icon-upload-alt"></i><br>Upload locally</span></div>
|
||||
<div class="dropzone" id="drop_locally"><span class="centered"><i class="icon-upload-alt"></i><br>{{ _('Upload locally') }}</span></div>
|
||||
<div class="dropzone_background" id="drop_locally_background"></div>
|
||||
<div class="dropzone" id="drop_sd"><span class="centered"><i class="icon-upload-alt"></i><br>Upload to SD<br><small data-bind="visible: !isSdReady()">(SD not initialized)</small></span></div>
|
||||
<div class="dropzone" id="drop_sd"><span class="centered"><i class="icon-upload-alt"></i><br>{{ _('Upload to SD') }}<br><small data-bind="visible: !isSdReady()">({{ _('SD not initialized') }})</small></span></div>
|
||||
<div class="dropzone_background" id="drop_sd_background"></div>
|
||||
{% else %}
|
||||
<div class="dropzone" id="drop"><span class="centered"><i class="icon-upload-alt"></i><br>Upload</span></div>
|
||||
<div class="dropzone" id="drop"><span class="centered"><i class="icon-upload-alt"></i><br>{{ _('Upload') }}</span></div>
|
||||
<div class="dropzone_background" id="drop_background"></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
@ -35,29 +31,29 @@
|
|||
<div id="confirmation_dialog" class="modal hide fade">
|
||||
<div class="modal-header">
|
||||
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">×</a>
|
||||
<h3>Are you sure?</h3>
|
||||
<h3>{{ _('Are you sure?') }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="confirmation_dialog_message"></p>
|
||||
<p>Are you sure you want to proceed?</p>
|
||||
<p>{{ _('Are you sure you want to proceed?') }}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a href="#" class="btn" data-dismiss="modal" aria-hidden="true">Cancel</a>
|
||||
<a href="#" class="btn btn-danger confirmation_dialog_acknowledge">Proceed</a>
|
||||
<a href="#" class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Cancel') }}</a>
|
||||
<a href="#" class="btn btn-danger confirmation_dialog_acknowledge">{{ _('Proceed') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="first_run_dialog" class="modal hide fade" data-backdrop="static" data-keyboard="false">
|
||||
<div class="modal-header">
|
||||
<h3><i class="icon-warning-sign"></i> Configure Access Control</h3>
|
||||
<h3><i class="icon-warning-sign"></i> {{ _('Configure Access Control') }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
{% trans %}<p>
|
||||
<strong>Please read the following, it is very important for your printer's health!</strong>
|
||||
</p>
|
||||
<p>
|
||||
OctoPrint by default now ships with Access Control enabled, meaning you won't be able to do anything with the
|
||||
printer unless you login first as a configured user. This is to <strong>prevent strangers - possibly with
|
||||
printer unless you login first as a configured user. This is just to <strong>prevent strangers - possibly with
|
||||
malicious intent - to gain access to your printer</strong> via the internet or another untrustworthy network
|
||||
and using it in such a way that it is damaged or worse (i.e. causes a fire).
|
||||
</p>
|
||||
|
|
@ -65,39 +61,39 @@
|
|||
It looks like you haven't configured access control yet. Please <strong>set up an username and password</strong> for the
|
||||
initial administrator account who will have full access to both the printer and OctoPrint's settings, then click
|
||||
on "Keep Access Control Enabled":
|
||||
</p>
|
||||
</p>{% endtrans %}
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group" data-bind="css: {success: validUsername()}">
|
||||
<label class="control-label" for="first_run_username">Username</label>
|
||||
<label class="control-label" for="first_run_username">{{ _('Username') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-medium" data-bind="value: username, valueUpdate: 'afterkeydown'">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" data-bind="css: {success: validPassword()}">
|
||||
<label class="control-label" for="first_run_username">Password</label>
|
||||
<label class="control-label" for="first_run_username">{{ _('Password') }}</label>
|
||||
<div class="controls">
|
||||
<input type="password" class="input-medium" data-bind="value: password, valueUpdate: 'afterkeydown'">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" data-bind="css: {error: passwordMismatch(), success: validPassword() && !passwordMismatch()}">
|
||||
<label class="control-label" for="first_run_username">Confirm Password</label>
|
||||
<label class="control-label" for="first_run_username">{{ _('Confirm Password') }}</label>
|
||||
<div class="controls">
|
||||
<input type="password" class="input-medium" data-bind="value: confirmedPassword, valueUpdate: 'afterkeydown'">
|
||||
<span class="help-inline" data-bind="visible: passwordMismatch()">Passwords don't match</span>
|
||||
<span class="help-inline" data-bind="visible: passwordMismatch()">{{ _('Passwords do not match') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<p>
|
||||
{% trans %}<p>
|
||||
<strong>Note:</strong> In case that your OctoPrint installation is only accessible from within a trustworthy network and you don't
|
||||
need Access Control for other reasons, you may alternatively disable Access Control. You should only
|
||||
do this if you are absolutely certain that only people you know and trust will be able to connect to it.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Do NOT underestimate the risk of an unsecured access from the internet to your printer!</strong>
|
||||
</p>
|
||||
</p>{% endtrans %}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a href="#" class="btn btn-danger" data-bind="click: disableAccessControl">Disable Access Control</a>
|
||||
<a href="#" class="btn btn-primary" data-bind="click: keepAccessControl, enable: validData(), css: {disabled: !validData()}">Keep Access Control Enabled</a>
|
||||
<a href="#" class="btn btn-danger" data-bind="click: disableAccessControl">{{ _('Disable Access Control') }}</a>
|
||||
<a href="#" class="btn btn-primary" data-bind="click: keepAccessControl, enable: validData(), css: {disabled: !validData()}">{{ _('Keep Access Control Enabled') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -42,6 +42,7 @@
|
|||
|
||||
var UI_API_KEY = "{{ uiApiKey }}";
|
||||
var VERSION = "{{ version }}";
|
||||
var LOCALE = "{{ g.locale }}";
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -53,13 +54,13 @@
|
|||
<ul class="nav pull-right">
|
||||
<li style="display: none;" data-bind="visible: loginState.isAdmin">
|
||||
<a id="navbar_show_settings" class="pull-right" href="#settings_dialog">
|
||||
<i class="icon-wrench"></i> Settings
|
||||
<i class="icon-wrench"></i> {{ _('Settings') }}
|
||||
</a>
|
||||
</li>
|
||||
{% if enableSystemMenu %}
|
||||
<li class="dropdown" style="display: none" data-bind="visible: loginState.isAdmin">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="icon-off"></i> System
|
||||
<i class="icon-off"></i> {{ _('System') }}
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu" data-bind="foreach: systemActions">
|
||||
|
|
@ -70,22 +71,22 @@
|
|||
{% if enableAccessControl %}
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="icon-user"></i> <span data-bind="text: loginState.userMenuText">Login</span>
|
||||
<i class="icon-user"></i> <span data-bind="text: loginState.userMenuText">{{ _('Login') }}</span>
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<div id="login_dropdown_loggedout" style="padding: 15px" class="dropdown-menu" data-bind="css: {hide: loginState.loggedIn(), 'dropdown-menu': !loginState.loggedIn()}">
|
||||
<label for="login_user">Username</label>
|
||||
<input type="text" id="login_user" placeholder="Username">
|
||||
<label for="login_pass">Password</label>
|
||||
<input type="password" id="login_pass" placeholder="Password">
|
||||
<label for="login_user">{{ _('Username') }}</label>
|
||||
<input type="text" id="login_user" placeholder="{{ _('Username') }}">
|
||||
<label for="login_pass">{{ _('Password') }}</label>
|
||||
<input type="password" id="login_pass" placeholder="{{ _('Password') }}">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="login_remember"> Remember me
|
||||
<input type="checkbox" id="login_remember"> {{ _('Remember me') }}
|
||||
</label>
|
||||
<button class="btn btn-block btn-primary" id="login_button" data-bind="click: loginState.login">Login</button>
|
||||
<button class="btn btn-block btn-primary" id="login_button" data-bind="click: loginState.login">{{ _('Login') }}</button>
|
||||
</div>
|
||||
<ul id="login_dropdown_loggedin" class="hide" data-bind="css: {hide: !loginState.loggedIn(), 'dropdown-menu': loginState.loggedIn()}">
|
||||
<li><a href="#" id="change_password_button" data-bind="click: function() { users.showChangePasswordDialog(loginState.currentUser()); }">Change Password</a></li>
|
||||
<li><a href="#" id="logout_button" data-bind="click: loginState.logout">Logout</a></li>
|
||||
<li><a href="#" id="change_password_button" data-bind="click: function() { users.showChangePasswordDialog(loginState.currentUser()); }">{{ _('Change Password') }}</a></li>
|
||||
<li><a href="#" id="logout_button" data-bind="click: loginState.logout">{{ _('Logout') }}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
|
@ -103,68 +104,68 @@
|
|||
</div>
|
||||
<div class="accordion-body collapse in" id="connection">
|
||||
<div class="accordion-inner">
|
||||
<label for="connection_ports" data-bind="css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()">Serial Port</label>
|
||||
<label for="connection_ports" data-bind="css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()">{{ _('Serial Port') }}</label>
|
||||
<select id="connection_ports" data-bind="options: portOptions, optionsCaption: 'AUTO', value: selectedPort, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"></select>
|
||||
<label for="connection_baudrates" data-bind="css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()">Baudrate</label>
|
||||
<label for="connection_baudrates" data-bind="css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()">{{ _('Baudrate') }}</label>
|
||||
<select id="connection_baudrates" data-bind="options: baudrateOptions, optionsCaption: 'AUTO', value: selectedBaudrate, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"></select>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="connection_save" data-bind="checked: saveSettings, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"> Save connection settings
|
||||
<input type="checkbox" id="connection_save" data-bind="checked: saveSettings, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"> {{ _('Save connection settings') }}
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="connection_autoconnect" data-bind="checked: settings.serial_autoconnect, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"> Auto-connect on server startup
|
||||
<input type="checkbox" id="connection_autoconnect" data-bind="checked: settings.serial_autoconnect, css: {disabled: !isErrorOrClosed()}, enable: isErrorOrClosed() && loginState.isUser()"> {{ _('Auto-connect on server startup') }}
|
||||
</label>
|
||||
<button class="btn btn-block" id="printer_connect" data-bind="click: connect, text: buttonText(), enable: loginState.isUser()">Connect</button>
|
||||
<button class="btn btn-block" id="printer_connect" data-bind="click: connect, text: buttonText(), enable: loginState.isUser()">{{ _('Connect') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-group" id="state_accordion">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#state"><i class="icon-info-sign"></i> State</a>
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#state"><i class="icon-info-sign"></i> {{ _('State') }}</a>
|
||||
</div>
|
||||
<div class="accordion-body collapse in" id="state">
|
||||
<div class="accordion-inner">
|
||||
Machine State: <strong data-bind="text: stateString"></strong><br>
|
||||
File: <strong data-bind="text: filename"></strong> <strong data-bind="visible: sd">(SD)</strong><br>
|
||||
Timelapse: <strong data-bind="text: timelapseString"></strong><br>
|
||||
{{ _('Machine State') }}: <strong data-bind="text: stateString"></strong><br>
|
||||
{{ _('File') }}: <strong data-bind="text: filename"></strong> <strong data-bind="visible: sd">(SD)</strong><br>
|
||||
{{ _('Timelapse') }}: <strong data-bind="text: timelapseString"></strong><br>
|
||||
<!-- ko foreach: filament -->
|
||||
<span data-bind="text: 'Filament (' + name() + '): '"></span><strong data-bind="text: formatFilament(data())"></strong><br>
|
||||
<!-- /ko -->
|
||||
Approx. Total Print Time: <strong data-bind="text: estimatedPrintTimeString"></strong><br>
|
||||
Print Time: <strong data-bind="text: printTimeString"></strong><br>
|
||||
Print Time Left: <strong data-bind="text: printTimeLeftString"></strong><br>
|
||||
Printed: <strong data-bind="text: byteString"></strong><br>
|
||||
{{ _('Approx. Total Print Time') }}: <strong data-bind="text: estimatedPrintTimeString"></strong><br>
|
||||
{{ _('Print Time') }}: <strong data-bind="text: printTimeString"></strong><br>
|
||||
{{ _('Print Time Left') }}: <strong data-bind="text: printTimeLeftString"></strong><br>
|
||||
{{ _('Printed') }}: <strong data-bind="text: byteString"></strong><br>
|
||||
|
||||
<div class="progress">
|
||||
<div class="bar" id="job_progressBar" data-bind="style: { width: progressString() + '%' }"></div>
|
||||
</div>
|
||||
|
||||
<div class="row-fluid print-control" style="display: none;" data-bind="visible: loginState.isUser">
|
||||
<button class="btn btn-primary span4" data-bind="click: print, enable: isOperational() && isReady() && !isPrinting() && loginState.isUser(), css: {'btn-danger': isPaused()}" id="job_print"><i class="icon-white" data-bind="css: {'icon-print': !isPaused(), 'icon-undo': isPaused()}"></i> <span data-bind="text: (isPaused() ? 'Restart' : 'Print')">Print</span></button>
|
||||
<button class="btn span4" id="job_pause" data-bind="click: pause, enable: isOperational() && (isPrinting() || isPaused()) && loginState.isUser(), css: {active: isPaused()}"><i class="icon-pause"></i> <span>Pause</span></button>
|
||||
<button class="btn span4" id="job_cancel" data-bind="click: cancel, enable: isOperational() && (isPrinting() || isPaused()) && loginState.isUser()"><i class="icon-stop"></i> Cancel</button>
|
||||
<button class="btn btn-primary span4" data-bind="click: print, enable: isOperational() && isReady() && !isPrinting() && loginState.isUser(), css: {'btn-danger': isPaused()}" id="job_print"><i class="icon-white" data-bind="css: {'icon-print': !isPaused(), 'icon-undo': isPaused()}"></i> <span data-bind="text: (isPaused() ? '{{ _('Restart') }}' : '{{ _('Print') }}')">{{ _('Print') }}</span></button>
|
||||
<button class="btn span4" id="job_pause" data-bind="click: pause, enable: isOperational() && (isPrinting() || isPaused()) && loginState.isUser(), css: {active: isPaused()}"><i class="icon-pause"></i> <span>{{ _('Pause') }}</span></button>
|
||||
<button class="btn span4" id="job_cancel" data-bind="click: cancel, enable: isOperational() && (isPrinting() || isPaused()) && loginState.isUser()"><i class="icon-stop"></i> {{ _('Cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-group" id="files_accordion">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#files"><i class="icon-list"></i> Files</a>
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#files"><i class="icon-list"></i> {{ _('Files') }}</a>
|
||||
|
||||
<div class="settings-trigger accordion-heading-button btn-group">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<span class="icon-wrench"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('name'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'name' ? 'visible' : 'hidden'}"></i> Sort by name (ascending)</a></li>
|
||||
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('upload'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'upload' ? 'visible' : 'hidden'}"></i> Sort by upload date (descending)</a></li>
|
||||
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('size'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'size' ? 'visible' : 'hidden'}"></i> Sort by file size (descending)</a></li>
|
||||
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('name'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'name' ? 'visible' : 'hidden'}"></i> {{ _('Sort by name') }} ({{ _('ascending') }})</a></li>
|
||||
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('upload'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'upload' ? 'visible' : 'hidden'}"></i> {{ _('Sort by upload date') }} ({{ _('descending') }})</a></li>
|
||||
<li><a href="#" data-bind="click: function() { $root.listHelper.changeSorting('size'); }"><i class="icon-ok" data-bind="style: {visibility: listHelper.currentSorting() == 'size' ? 'visible' : 'hidden'}"></i> {{ _('Sort by file size') }} ({{ _('descending') }})</a></li>
|
||||
{% if enableSdSupport %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="#" data-bind="click: function() { $root.listHelper.toggleFilter('local'); }"><i class="icon-ok" data-bind="style: {visibility: _.contains(listHelper.currentFilters(), 'local') ? 'visible' : 'hidden'}"></i> Only show files stored locally</a></li>
|
||||
<li><a href="#" data-bind="click: function() { $root.listHelper.toggleFilter('sd'); }"><i class="icon-ok" data-bind="style: {visibility: _.contains(listHelper.currentFilters(), 'sd') ? 'visible' : 'hidden'}"></i> Only show files stored on SD</a></li>
|
||||
<li><a href="#" data-bind="click: function() { $root.listHelper.toggleFilter('local'); }"><i class="icon-ok" data-bind="style: {visibility: _.contains(listHelper.currentFilters(), 'local') ? 'visible' : 'hidden'}"></i> {{ _('Only show files stored locally') }}</a></li>
|
||||
<li><a href="#" data-bind="click: function() { $root.listHelper.toggleFilter('sd'); }"><i class="icon-ok" data-bind="style: {visibility: _.contains(listHelper.currentFilters(), 'sd') ? 'visible' : 'hidden'}"></i> {{ _('Only show files stored on SD') }}</a></li>
|
||||
{% endif %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="#" data-bind="click: function() { $root.listHelper.toggleFilter('printed'); }"><i class="icon-ok" data-bind="style: {visibility: _.contains(listHelper.currentFilters(), 'printed') ? 'visible' : 'hidden'}"></i> Hide successfully printed files</a></li>
|
||||
<li><a href="#" data-bind="click: function() { $root.listHelper.toggleFilter('printed'); }"><i class="icon-ok" data-bind="style: {visibility: _.contains(listHelper.currentFilters(), 'printed') ? 'visible' : 'hidden'}"></i> {{ _('Hide successfully printed files') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
|
@ -174,9 +175,9 @@
|
|||
<span class="icon-sd-black-14"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li data-bind="visible: !isSdReady()"><a href="#" data-bind="click: function() { $root.initSdCard(); }"><i class="icon-flag"></i> Initialize SD card</a></li>
|
||||
<li data-bind="visible: isSdReady()"><a href="#" data-bind="click: function() { $root.refreshSdFiles(); }"><i class="icon-refresh"></i> Refresh SD files</a></li>
|
||||
<li data-bind="visible: isSdReady()"><a href="#" data-bind="click: function() { $root.releaseSdCard(); }"><i class="icon-eject"></i> Release SD card</a></li>
|
||||
<li data-bind="visible: !isSdReady()"><a href="#" data-bind="click: function() { $root.initSdCard(); }"><i class="icon-flag"></i> {{ _('Initialize SD card') }}</a></li>
|
||||
<li data-bind="visible: isSdReady()"><a href="#" data-bind="click: function() { $root.refreshSdFiles(); }"><i class="icon-refresh"></i> {{ _('Refresh SD files') }}</a></li>
|
||||
<li data-bind="visible: isSdReady()"><a href="#" data-bind="click: function() { $root.releaseSdCard(); }"><i class="icon-eject"></i> {{ _('Release SD card') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
@ -184,43 +185,43 @@
|
|||
<div class="accordion-body collapse in overflow_visible" id="files">
|
||||
<div class="accordion-inner">
|
||||
<form class="form-search">
|
||||
<input type="text" class="input-block search-query" data-bind="value: searchQuery, valueUpdate: 'input'" placeholder="Search...">
|
||||
<input type="text" class="input-block search-query" data-bind="value: searchQuery, valueUpdate: 'input'" placeholder="{{ _('Search...') }}">
|
||||
</form>
|
||||
<div class="gcode_files" data-bind="slimScrolledForeach: listHelper.paginatedItems">
|
||||
<div class="entry" data-bind="attr: { id: $root.getEntryId($data) }">
|
||||
<div class="title" data-bind="css: $root.getSuccessClass($data), style: { 'font-weight': $root.listHelper.isSelected($data) ? 'bold' : 'normal' }, text: name"></div>
|
||||
<div class="uploaded">Uploaded: <span data-bind="text: formatTimeAgo(date)"></span></div>
|
||||
<div class="size">Size: <span data-bind="text: formatSize(size)"></span></div>
|
||||
<div class="uploaded">{{ _('Uploaded') }}: <span data-bind="text: formatTimeAgo(date)"></span></div>
|
||||
<div class="size">{{ _('Size') }}: <span data-bind="text: formatSize(size)"></span></div>
|
||||
<div class="additionalInfo hide" data-bind="html: $root.getAdditionalData($data)"></div>
|
||||
<div class="btn-group action-buttons">
|
||||
<div class="btn btn-mini toggleAdditionalData" data-bind="click: function() { if ($root.enableAdditionalData($data)) { $root.toggleAdditionalData($data); } else { return; } }, css: { disabled: !$root.enableAdditionalData($data) }"><i class="icon-chevron-down"></i></div>
|
||||
<a class="btn btn-mini" data-bind="attr: {href: $root.downloadLink($data), css: {disabled: !$root.downloadLink($data)}}"><i class="icon-download-alt" title="Download"></i></a>
|
||||
<div class="btn btn-mini" data-bind="click: function() { if ($root.enableRemove($data)) { $root.removeFile($data.name); } else { return; } }, css: {disabled: !$root.enableRemove($data)}"><i class="icon-trash" title="Remove"></i></div>
|
||||
<div class="btn btn-mini" data-bind="click: function() { if ($root.enableSelect($data)) { $root.loadFile($data.name, false); } else { return; } }, css: {disabled: !$root.enableSelect($data)}"><i class="icon-folder-open" title="Load"></i></div>
|
||||
<div class="btn btn-mini" data-bind="click: function() { if ($root.enableSelect($data)) { $root.loadFile($data.name, true); } else { return; } }, css: {disabled: !$root.enableSelect($data)}"><i class="icon-print" title="Load and Print"></i></div>
|
||||
<a class="btn btn-mini" data-bind="attr: {href: $root.downloadLink($data), css: {disabled: !$root.downloadLink($data)}}"><i class="icon-download-alt" title="{{ _('Download') }}"></i></a>
|
||||
<div class="btn btn-mini" data-bind="click: function() { if ($root.enableRemove($data)) { $root.removeFile($data.name); } else { return; } }, css: {disabled: !$root.enableRemove($data)}"><i class="icon-trash" title="{{ _('Remove') }}"></i></div>
|
||||
<div class="btn btn-mini" data-bind="click: function() { if ($root.enableSelect($data)) { $root.loadFile($data.name, false); } else { return; } }, css: {disabled: !$root.enableSelect($data)}"><i class="icon-folder-open" title="{{ _('Load') }}"></i></div>
|
||||
<div class="btn btn-mini" data-bind="click: function() { if ($root.enableSelect($data)) { $root.loadFile($data.name, true); } else { return; } }, css: {disabled: !$root.enableSelect($data)}"><i class="icon-print" title="{{ _('Load and Print') }}"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="muted text-right">
|
||||
<small>Free: <span data-bind="text: freeSpaceString"></span></small>
|
||||
<small>{{ _('Free') }}: <span data-bind="text: freeSpaceString"></span></small>
|
||||
</div>
|
||||
<div style="display: none;" data-bind="visible: loginState.isUser">
|
||||
<div class="row-fluid upload-buttons">
|
||||
{% if enableSdSupport %}
|
||||
<span class="btn btn-primary fileinput-button span6" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px">
|
||||
<i class="icon-upload-alt icon-white"></i>
|
||||
<span>Upload</span>
|
||||
<span>{{ _('Upload') }}</span>
|
||||
<input id="gcode_upload" type="file" name="file" class="fileinput-button" data-bind="enable: loginState.isUser()">
|
||||
</span>
|
||||
<span class="btn btn-primary fileinput-button span6" data-bind="css: {disabled: !$root.loginState.isUser() || !$root.isSdReady()}" style="margin-bottom: 10px">
|
||||
<i class="icon-upload-alt icon-white"></i>
|
||||
<span>Upload to SD</span>
|
||||
<span>{{ _('Upload to SD') }}</span>
|
||||
<input id="gcode_upload_sd" type="file" name="file" class="fileinput-button" data-bind="enable: loginState.isUser() && isSdReady()">
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="btn btn-primary fileinput-button span12" data-bind="css: {disabled: !$root.loginState.isUser()}" style="margin-bottom: 10px">
|
||||
<i class="icon-upload-alt icon-white"></i>
|
||||
<span>Upload</span>
|
||||
<span>{{ _('Upload') }}</span>
|
||||
<input id="gcode_upload" type="file" name="file" class="fileinput-button" data-bind="enable: loginState.isUser()">
|
||||
</span>
|
||||
{% endif %}
|
||||
|
|
@ -229,7 +230,7 @@
|
|||
<div class="bar" style="width: 0%"></div>
|
||||
</div>
|
||||
<div>
|
||||
<small>Hint: You can also drag and drop files on this page to upload them.</small>
|
||||
<small>{{ _('Hint: You can also drag and drop files on this page to upload them.') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -239,11 +240,11 @@
|
|||
|
||||
<div class="span8 tabbable">
|
||||
<ul class="nav nav-tabs" id="tabs">
|
||||
<li class="active"><a href="#temp" data-toggle="tab">Temperature</a></li>
|
||||
<li><a href="#control" data-toggle="tab">Control</a></li>
|
||||
{% if enableGCodeVisualizer %}<li><a href="#gcode" data-toggle="tab">GCode Viewer</a></li>{% endif %}
|
||||
<li><a href="#term" data-toggle="tab">Terminal</a></li>
|
||||
{% if enableTimelapse %}<li><a href="#timelapse" data-toggle="tab">Timelapse</a></li>{% endif %}
|
||||
<li class="active"><a href="#temp" data-toggle="tab">{{ _('Temperature') }}</a></li>
|
||||
<li><a href="#control" data-toggle="tab">{{ _('Control') }}</a></li>
|
||||
{% if enableGCodeVisualizer %}<li><a href="#gcode" data-toggle="tab">{{ _('GCode Viewer') }}</a></li>{% endif %}
|
||||
<li><a href="#term" data-toggle="tab">{{ _('Terminal') }}</a></li>
|
||||
{% if enableTimelapse %}<li><a href="#timelapse" data-toggle="tab">{{ _('Timelapse') }}</a></li>{% endif %}
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
|
|
@ -258,9 +259,9 @@
|
|||
<table class="table table-bordered table-hover" style="table-layout: fixed; width: 100%; margin-top: 20px">
|
||||
<tr>
|
||||
<th style="width: 18%"></th>
|
||||
<th style="width: 12%; text-align: right">Actual</th>
|
||||
<th style="width: 35%">Target</th>
|
||||
<th style="width: 35%">Offset</th>
|
||||
<th style="width: 12%; text-align: right">{{ _('Actual') }}</th>
|
||||
<th style="width: 35%">{{ _('Target') }}</th>
|
||||
<th style="width: 35%">{{ _('Offset') }}</th>
|
||||
</tr>
|
||||
<!-- ko foreach: tools -->
|
||||
<tr data-bind="template: { name: 'temprow-template' }"></tr>
|
||||
|
|
@ -276,7 +277,7 @@
|
|||
<input type="text" class="input-mini text-right tempInput" data-bind="attr: {placeholder: cleanTemperature(target()) }, value: newTarget, enable: $root.isOperational() && $root.loginState.isUser(), event: { keyup: function(d, e) {$root.handleEnter(e, 'target', $data);} }">
|
||||
<span class="add-on">°C</span>
|
||||
<div class="btn-group">
|
||||
<button type="submit" data-bind="click: $parent.setTarget, enable: $root.isOperational() && $root.loginState.isUser()" class="btn">Set</button>
|
||||
<button type="submit" data-bind="click: $parent.setTarget, enable: $root.isOperational() && $root.loginState.isUser()" class="btn">{{ _('Set') }}</button>
|
||||
<button class="btn dropdown-toggle" data-toggle="dropdown" data-bind="enable: $root.isOperational() && $root.loginState.isUser()">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
|
|
@ -288,7 +289,7 @@
|
|||
<!-- /ko -->
|
||||
<li class="divider"></li>
|
||||
<li>
|
||||
<a href="#" data-bind="click: $root.setTargetToZero">Off</a>
|
||||
<a href="#" data-bind="click: $root.setTargetToZero">{{ _('Off') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -298,7 +299,7 @@
|
|||
<div class="input-append">
|
||||
<input type="number" min="-50" max="50" class="input-mini text-right tempInput" data-bind="attr: {placeholder: offset}, value: newOffset, enable: $root.isOperational() && $root.loginState.isUser(), event: { keyup: function(d, e) {$root.handleEnter(e, 'offset', $data);} }">
|
||||
<span class="add-on">°C</span>
|
||||
<button type="submit" data-bind="click: $root.setOffset, enable: $root.isOperational() && $root.loginState.isUser()" class="btn">Set</button>
|
||||
<button type="submit" data-bind="click: $root.setOffset, enable: $root.isOperational() && $root.loginState.isUser()" class="btn">{{ _('Set') }}</button>
|
||||
</div>
|
||||
</td>
|
||||
</script>
|
||||
|
|
@ -356,28 +357,28 @@
|
|||
<div>
|
||||
<div class="btn-group control-box">
|
||||
<button class="btn dropdown-toggle" data-toggle="dropdown" data-bind="enable: isOperational() && !isPrinting() && !isPaused() && loginState.isUser()">
|
||||
Select Tool...
|
||||
{{ _('Select Tool...') }}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" data-bind="foreach: tools">
|
||||
<li><a href="#" data-bind="click: $root.sendSelectToolCommand, text: 'Select ' + name(), enable: $root.isOperational() && !$root.isPrinting() && !$root.isPaused() && $root.loginState.isUser()"></a></li>
|
||||
<li><a href="#" data-bind="click: $root.sendSelectToolCommand, text: name(), enable: $root.isOperational() && !$root.isPrinting() && !$root.isPaused() && $root.loginState.isUser()"></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="input-append control-box">
|
||||
<input type="text" class="input-mini text-right" data-bind="value: extrusionAmount, enable: isOperational() && !isPrinting() && loginState.isUser(), attr: {placeholder: settings.printer_defaultExtrusionLength}">
|
||||
<span class="add-on">mm</span>
|
||||
</div>
|
||||
<button class="btn btn-block control-box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendExtrudeCommand() }">Extrude</button>
|
||||
<button class="btn btn-block control-box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendRetractCommand() }">Retract</button>
|
||||
<button class="btn btn-block control-box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendExtrudeCommand() }">{{ _('Extrude') }}</button>
|
||||
<button class="btn btn-block control-box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendRetractCommand() }">{{ _('Retract') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- General control panel -->
|
||||
<div class="jog-panel" style="display: none;" data-bind="visible: loginState.isUser">
|
||||
<h1>General</h1>
|
||||
<h1>{{ _('General') }}</h1>
|
||||
<div>
|
||||
<button class="btn btn-block control-box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendCustomCommand({type:'command',command:'M18'}) }">Motors off</button>
|
||||
<button class="btn btn-block control-box" data-bind="enable: isOperational() && loginState.isUser(), click: function() { $root.sendCustomCommand({type:'command',command:'M106'}) }">Fans on</button>
|
||||
<button class="btn btn-block control-box" data-bind="enable: isOperational() && loginState.isUser(), click: function() { $root.sendCustomCommand({type:'command',command:'M106 S0'}) }">Fans off</button>
|
||||
<button class="btn btn-block control-box" data-bind="enable: isOperational() && !isPrinting() && loginState.isUser(), click: function() { $root.sendCustomCommand({type:'command',command:'M18'}) }">{{ _('Motors off') }}</button>
|
||||
<button class="btn btn-block control-box" data-bind="enable: isOperational() && loginState.isUser(), click: function() { $root.sendCustomCommand({type:'command',command:'M106'}) }">{{ _('Fans on') }}</button>
|
||||
<button class="btn btn-block control-box" data-bind="enable: isOperational() && loginState.isUser(), click: function() { $root.sendCustomCommand({type:'command',command:'M106 S0'}) }">{{ _('Fans off') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -441,38 +442,38 @@
|
|||
|
||||
<p>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: renderer_syncProgress">Sync with job progress
|
||||
<input type="checkbox" data-bind="checked: renderer_syncProgress">{{ _('Sync with job progress') }}
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: renderer_centerViewport">Center viewport on model
|
||||
<input type="checkbox" data-bind="checked: renderer_centerViewport">{{ _('Center viewport on model') }}
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: renderer_zoomOnModel">Zoom in on model
|
||||
<input type="checkbox" data-bind="checked: renderer_zoomOnModel">{{ _('Zoom in on model') }}
|
||||
</label>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: renderer_showMoves">Show moves
|
||||
<input type="checkbox" data-bind="checked: renderer_showMoves">{{ _('Show moves') }}
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: renderer_showRetracts">Show retracts
|
||||
<input type="checkbox" data-bind="checked: renderer_showRetracts">{{ _('Show retracts') }}
|
||||
</label>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: renderer_showPrevious">Also show previous layer
|
||||
<input type="checkbox" data-bind="checked: renderer_showPrevious">{{ _('Also show previous layer') }}
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: renderer_showNext">Also show next layer
|
||||
<input type="checkbox" data-bind="checked: renderer_showNext">{{ _('Also show next layer') }}
|
||||
</label>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<button class="btn btn-block" data-bind="click: reload, enable: enableReload">Reload</button>
|
||||
<button class="btn btn-block" data-bind="click: reload, enable: enableReload">{{ _('Reload') }}</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -499,7 +500,7 @@
|
|||
<div class="tab-pane" id="term">
|
||||
<pre id="terminal-output" class="pre-scrollable"></pre>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="terminal-autoscroll" data-bind="checked: autoscrollEnabled"> Autoscroll
|
||||
<input type="checkbox" id="terminal-autoscroll" data-bind="checked: autoscrollEnabled"> {{ _('Autoscroll') }}
|
||||
</label>
|
||||
<div data-bind="foreach: filters">
|
||||
<label class="checkbox">
|
||||
|
|
@ -509,57 +510,57 @@
|
|||
|
||||
<div class="input-append" style="display: none;" data-bind="visible: loginState.isUser">
|
||||
<input type="text" id="terminal-command" data-bind="value: command, event: { keyup: function(d,e) { return handleKeyUp(e); }, keydown: function(d,e) { return handleKeyDown(e); } }, enable: isOperational() && loginState.isUser()">
|
||||
<button class="btn" type="button" id="terminal-send" data-bind="click: sendCommand, enable: isOperational() && loginState.isUser()">Send</button>
|
||||
<button class="btn" type="button" id="terminal-send" data-bind="click: sendCommand, enable: isOperational() && loginState.isUser()">{{ _('Send') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
{% if enableTimelapse %}
|
||||
<div class="tab-pane" id="timelapse">
|
||||
<div style="display: none;" data-bind="visible: loginState.isUser">
|
||||
<h1>Timelapse Configuration</h1>
|
||||
<h1>{{ _('Timelapse Configuration') }}</h1>
|
||||
|
||||
<label for="webcam_timelapse_mode">Timelapse Mode</label>
|
||||
<label for="webcam_timelapse_mode">{{ _('Timelapse Mode') }}</label>
|
||||
<select id="webcam_timelapse_mode" data-bind="value: timelapseType, enable: isOperational() && !isPrinting() && loginState.isUser()">
|
||||
<option value="off">Off</option>
|
||||
<option value="zchange">On Z Change</option>
|
||||
<option value="timed">Timed</option>
|
||||
<option value="off">{{ _('Off') }}</option>
|
||||
<option value="zchange">{{ _('On Z Change') }}</option>
|
||||
<option value="timed">{{ _('Timed') }}</option>
|
||||
</select>
|
||||
|
||||
<label for="webcam_timelapse_postRoll">Timelapse post roll (in rendered seconds)</label>
|
||||
<label for="webcam_timelapse_postRoll">{{ _('Timelapse post roll (in rendered seconds)') }}</label>
|
||||
<div class="input-append">
|
||||
<input type="text" class="input-mini" id="webcam_timelapse_postRoll" data-bind="value: timelapsePostRoll, valueUpdate: 'afterkeydown', enable: isOperational() && !isPrinting() && loginState.isUser() && timelapseTypeSelected()">
|
||||
<span class="add-on">sec</span>
|
||||
<span class="add-on">{{ _('sec') }}</span>
|
||||
</div>
|
||||
|
||||
<div id="webcam_timelapse_timedsettings" data-bind="visible: intervalInputEnabled">
|
||||
<label for="webcam_timelapse_interval">Interval</label>
|
||||
<label for="webcam_timelapse_interval">{{ _('Interval') }}</label>
|
||||
<div class="input-append">
|
||||
<input type="text" class="input-mini" id="webcam_timelapse_interval" data-bind="value: timelapseTimedInterval, valueUpdate: 'afterkeydown', enable: isOperational() && !isPrinting() && loginState.isUser()">
|
||||
<span class="add-on">sec</span>
|
||||
<span class="add-on">{{ _('sec') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-bind="visible: loginState.isAdmin">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: persist"> Save as default
|
||||
<input type="checkbox" data-bind="checked: persist"> {{ _('Save as default') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="btn" data-bind="click: save, enable: saveButtonEnabled">Save config</button>
|
||||
<button class="btn" data-bind="click: save, enable: saveButtonEnabled">{{ _('Save config') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Finished Timelapses</h1>
|
||||
<h1>{{ _('Finished Timelapses') }}</h1>
|
||||
|
||||
<div class="pull-right">
|
||||
<small>Sort by: <a href="#" data-bind="click: function() { listHelper.changeSorting('name'); }">Name (ascending)</a> | <a href="#" data-bind="click: function() { listHelper.changeSorting('creation'); }">Creation date (descending)</a> | <a href="#" data-bind="click: function() { listHelper.changeSorting('size'); }">Size (descending)</a></small>
|
||||
<small>{{ _('Sort by') }}: <a href="#" data-bind="click: function() { listHelper.changeSorting('name'); }">{{ _('Name') }} ({{ _('ascending') }})</a> | <a href="#" data-bind="click: function() { listHelper.changeSorting('creation'); }">{{ _('Creation date') }} ({{ _('descending') }})</a> | <a href="#" data-bind="click: function() { listHelper.changeSorting('size'); }">{{ _('Size') }} ({{ _('descending') }})</a></small>
|
||||
</div>
|
||||
<table class="table table-striped table-hover table-condensed table-hover" id="timelapse_files">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="timelapse_files_name">Name</th>
|
||||
<th class="timelapse_files_size">Size</th>
|
||||
<th class="timelapse_files_action">Action</th>
|
||||
<th class="timelapse_files_name">{{ _('Name') }}</th>
|
||||
<th class="timelapse_files_size">{{ _('Size') }}</th>
|
||||
<th class="timelapse_files_action">{{ _('Action') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-bind="foreach: listHelper.paginatedItems">
|
||||
|
|
@ -588,13 +589,13 @@
|
|||
</div>
|
||||
<div class="footer">
|
||||
<ul class="pull-left muted">
|
||||
<li><small>Version: <span class="version">{{ version }}</span></small></li>
|
||||
<li><small>{{ _('Version') }}: <span class="version">{{ version }}</span></small></li>
|
||||
</ul>
|
||||
<ul class="pull-right">
|
||||
<li><a href="http://octoprint.org"><i class="icon-home"></i> Homepage</a></li>
|
||||
<li><a href="https://github.com/foosel/OctoPrint/"><i class="icon-download"></i> Sourcecode</a></li>
|
||||
<li><a href="https://github.com/foosel/OctoPrint/wiki"><i class="icon-book"></i> Documentation</a></li>
|
||||
<li><a href="https://github.com/foosel/OctoPrint/issues"><i class="icon-flag"></i> Bugs and Requests</a></li>
|
||||
<li><a href="http://octoprint.org"><i class="icon-home"></i> {{ _('Homepage') }}</a></li>
|
||||
<li><a href="https://github.com/foosel/OctoPrint/"><i class="icon-download"></i> {{ _('Sourcecode') }}</a></li>
|
||||
<li><a href="https://github.com/foosel/OctoPrint/wiki"><i class="icon-book"></i> {{ _('Documentation') }}</a></li>
|
||||
<li><a href="https://github.com/foosel/OctoPrint/issues"><i class="icon-flag"></i> {{ _('Bugs and Requests') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -607,6 +608,7 @@
|
|||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/underscore-min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/underscore.string.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/knockout.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/babel.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/avltree.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap/bootstrap.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/bootstrap/bootstrap-modalmanager.js') }}"></script>
|
||||
|
|
@ -620,7 +622,7 @@
|
|||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.fileupload.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/jquery/jquery.slimscroll.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/sockjs-0.3.4.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/moment.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/moment-with-locales.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/pusher.color.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/detectmobilebrowser.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/md5.min.js') }}"></script>
|
||||
|
|
@ -650,6 +652,12 @@
|
|||
<script type="text/javascript" src="{{ url_for('static', filename='js/app/main.js') }}"></script>
|
||||
<!-- /Include OctoPrint files -->
|
||||
|
||||
<!-- Include i18n language files -->
|
||||
{% if g.locale != 'en' %}
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/i18n/%s.js' % g.locale) }}"></script>
|
||||
{% endif %}
|
||||
<!-- /Include i18n language files -->
|
||||
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='gcodeviewer/js/ui.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='gcodeviewer/js/gCodeReader.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='gcodeviewer/js/renderer.js') }}"></script>
|
||||
|
|
|
|||
|
|
@ -1,39 +1,39 @@
|
|||
<div id="settings_dialog" class="modal hide fade container" tabindex="-1" role="dialog" aria-labelledby="settings_dialog_label" aria-hidden="true">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h3 id="settings_dialog_label">OctoPrint Settings</h3>
|
||||
<h3 id="settings_dialog_label">{{ _('OctoPrint Settings') }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="tabbable">
|
||||
<ul class="nav nav-list span4" id="settingsTabs">
|
||||
<li class="nav-header">Printer</li>
|
||||
<li class="active"><a href="#settings_serialConnection" data-toggle="tab">Serial Connection</a></li>
|
||||
<li><a href="#settings_printerParameters" data-toggle="tab">Printer Parameters</a></li>
|
||||
<li><a href="#settings_temperature" data-toggle="tab">Temperatures</a></li>
|
||||
<li><a href="#settings_terminalFilters" data-toggle="tab">Terminal filters</a></li>
|
||||
<li class="nav-header">Features</li>
|
||||
<li><a href="#settings_features" data-toggle="tab">Features</a></li>
|
||||
<li><a href="#settings_webcam" data-toggle="tab">Webcam</a></li>
|
||||
<li><a href="#settings_cura" data-toggle="tab">Cura</a></li>
|
||||
{% if enableAccessControl %}<li><a href="#settings_users" data-toggle="tab">Access Control</a></li>{% endif %}
|
||||
<li><a href="#settings_api" data-toggle="tab">Api</a></li>
|
||||
<li class="nav-header">{{ _('Printer') }}</li>
|
||||
<li class="active"><a href="#settings_serialConnection" data-toggle="tab">{{ _('Serial Connection') }}</a></li>
|
||||
<li><a href="#settings_printerParameters" data-toggle="tab">{{ _('Printer Parameters') }}</a></li>
|
||||
<li><a href="#settings_temperature" data-toggle="tab">{{ _('Temperatures') }}</a></li>
|
||||
<li><a href="#settings_terminalFilters" data-toggle="tab">{{ _('Terminal filters') }}</a></li>
|
||||
<li class="nav-header">{{ _('Features') }}</li>
|
||||
<li><a href="#settings_features" data-toggle="tab">{{ _('Features') }}</a></li>
|
||||
<li><a href="#settings_webcam" data-toggle="tab">{{ _('Webcam') }}</a></li>
|
||||
<li><a href="#settings_cura" data-toggle="tab">{{ _('Cura') }}</a></li>
|
||||
{% if enableAccessControl %}<li><a href="#settings_users" data-toggle="tab">{{ _('Access Control') }}</a></li>{% endif %}
|
||||
<li><a href="#settings_api" data-toggle="tab">{{ _('API') }}</a></li>
|
||||
<li class="nav-header">OctoPrint</li>
|
||||
<li><a href="#settings_folder" data-toggle="tab">Folders</a></li>
|
||||
<li><a href="#settings_appearance" data-toggle="tab">Appearance</a></li>
|
||||
<li><a href="#settings_logs" data-toggle="tab">Logs</a></li>
|
||||
<li><a href="#settings_folder" data-toggle="tab">{{ _('Folders') }}</a></li>
|
||||
<li><a href="#settings_appearance" data-toggle="tab">{{ _('Appearance') }}</a></li>
|
||||
<li><a href="#settings_logs" data-toggle="tab">{{ _('Logs') }}</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content span8">
|
||||
<div class="tab-pane active" id="settings_serialConnection">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-serialPort">Serial Port</label>
|
||||
<label class="control-label" for="settings-serialPort">{{ _('Serial Port') }}</label>
|
||||
<div class="controls">
|
||||
<select id="settings-serialPort" data-bind="options: serial_portOptions, optionsCaption: 'AUTO', value: serial_port"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-baudrate">Baudrate</label>
|
||||
<label class="control-label" for="settings-baudrate">{{ _('Baudrate') }}</label>
|
||||
<div class="controls">
|
||||
<select id="settings-baudrate" data-bind="options: serial_baudrateOptions, optionsCaption: 'AUTO', value: serial_baudrate"></select>
|
||||
</div>
|
||||
|
|
@ -41,12 +41,12 @@
|
|||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: serial_autoconnect" id="settings-serialAutoconnect"> Auto-connect to printer on server start
|
||||
<input type="checkbox" data-bind="checked: serial_autoconnect" id="settings-serialAutoconnect"> {{ _('Auto-connect to printer on server start') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-serialTimeoutCommunication">Communication timeout</label>
|
||||
<label class="control-label" for="settings-serialTimeoutCommunication">{{ _('Communication timeout') }}</label>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<input type="number" step="any" min="0" class="input-mini text-right" data-bind="value: serial_timeoutCommunication" id="settings-serialTimeoutCommunication">
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-movementSpeedE">Temperature timeout</label>
|
||||
<label class="control-label" for="settings-movementSpeedE">{{ _('Temperature timeout') }}</label>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<input type="number" step="any" min="0" class="input-mini text-right" data-bind="value: serial_timeoutTemperature" id="settings-serialTimeoutTemperature">
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-serialTimeoutSdStatus">SD status timeout</label>
|
||||
<label class="control-label" for="settings-serialTimeoutSdStatus">{{ _('SD status timeout') }}</label>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<input type="number" step="any" min="0" class="input-mini text-right" data-bind="value: serial_timeoutSdStatus" id="settings-serialTimeoutSdStatus">
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-serialTimeoutConnection">Connection timeout</label>
|
||||
<label class="control-label" for="settings-serialTimeoutConnection">{{ _('Connection timeout') }}</label>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<input type="number" step="any" min="0" class="input-mini text-right" data-bind="value: serial_timeoutConnection" id="settings-serialTimeoutConnection">
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-serialTimeoutDetection">Autodetection timeout</label>
|
||||
<label class="control-label" for="settings-serialTimeoutDetection">{{ _('Autodetection timeout') }}</label>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<input type="number" step="any" min="0" class="input-mini text-right" data-bind="value: serial_timeoutDetection" id="settings-serialTimeoutDetection">
|
||||
|
|
@ -93,7 +93,7 @@
|
|||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: serial_log" id="settings-serialLog"> Log communication to serial.log (might negatively impact performance) <span class="label label-important">Warning</span>
|
||||
<input type="checkbox" data-bind="checked: serial_log" id="settings-serialLog"> {{ _('Log communication to serial.log (might negatively impact performance)') }} <span class="label label-important">{{ _('Warning') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -102,39 +102,39 @@
|
|||
<div class="tab-pane" id="settings_printerParameters">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Axis</label>
|
||||
<label class="control-label">{{ _('Axis') }}</label>
|
||||
<div class="controls form-inline">
|
||||
<label>X:</label>
|
||||
<label>{{ _('X') }}:</label>
|
||||
<div class="input-append">
|
||||
<input type="number" class="input-mini text-right" data-bind="value: printer_movementSpeedX" id="settings-movementSpeedX">
|
||||
<span class="add-on">mm/min</span>
|
||||
</div>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: printer_invertX" id="settings-printerInvertX"> Invert control
|
||||
<input type="checkbox" data-bind="checked: printer_invertX" id="settings-printerInvertX"> {{ _('Invert control') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="controls form-inline">
|
||||
<label>Y:</label>
|
||||
<label>{{ _('Y') }}:</label>
|
||||
<div class="input-append">
|
||||
<input type="number" class="input-mini text-right" data-bind="value: printer_movementSpeedY" id="settings-movementSpeedY">
|
||||
<span class="add-on">mm/min</span>
|
||||
</div>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: printer_invertY" id="settings-printerInvertY"> Invert control
|
||||
<input type="checkbox" data-bind="checked: printer_invertY" id="settings-printerInvertY"> {{ _('Invert control') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="controls form-inline">
|
||||
<label>Z:</label>
|
||||
<label>{{ _('Z') }}:</label>
|
||||
<div class="input-append">
|
||||
<input type="number" class="input-mini text-right" data-bind="value: printer_movementSpeedZ" id="settings-movementSpeedZ">
|
||||
<span class="add-on">mm/min</span>
|
||||
</div>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: printer_invertZ" id="settings-printerInvertZ"> Invert control
|
||||
<input type="checkbox" data-bind="checked: printer_invertZ" id="settings-printerInvertZ"> {{ _('Invert control') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="controls form-inline">
|
||||
<label>E:</label>
|
||||
<label>{{ _('E') }}:</label>
|
||||
<div class="input-append">
|
||||
<input type="number" class="input-mini text-right" data-bind="value: printer_movementSpeedE" id="settings-movementSpeedE">
|
||||
<span class="add-on">mm/min</span>
|
||||
|
|
@ -142,19 +142,19 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-defaultExtrusionLength">Default extrusion length</label>
|
||||
<label class="control-label" for="settings-defaultExtrusionLength">{{ _('Default extrusion length') }}</label>
|
||||
<div class="controls">
|
||||
<input type="number" class="input-mini text-right" min="1" data-bind="value: printer_defaultExtrusionLength" id="settings-defaultExtrusionLength">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-numExtruders">Number of Extruders</label>
|
||||
<label class="control-label" for="settings-numExtruders">{{ _('Number of Extruders') }}</label>
|
||||
<div class="controls">
|
||||
<input type="number" class="input-mini text-right" min="1" max="5" data-bind="value: printer_numExtruders" id="settings-numExtruders">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-extruderOffsets">Extruder Offsets</label>
|
||||
<label class="control-label" for="settings-extruderOffsets">{{ _('Extruder Offsets') }}</label>
|
||||
<!-- ko foreach: ko_printer_extruderOffsets -->
|
||||
<div class="controls form-inline">
|
||||
<label>X:</label>
|
||||
|
|
@ -171,21 +171,21 @@
|
|||
<!-- /ko -->
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-bedSize">Bed Size</label>
|
||||
<label class="control-label" for="settings-bedSize">{{ _('Bed Size') }}</label>
|
||||
<div class="controls form-inline" data-bind="ifnot: printer_bedCircular">
|
||||
<label>X:</label>
|
||||
<label>{{ _('X') }}:</label>
|
||||
<div class="input-append">
|
||||
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: printer_bedDimensionX" id="settings-bedX">
|
||||
<span class="add-on">mm</span>
|
||||
</div>
|
||||
<label>Y:</label>
|
||||
<label>{{ _('Y') }}:</label>
|
||||
<div class="input-append">
|
||||
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: printer_bedDimensionY" id="settings-bedY">
|
||||
<span class="add-on">mm</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls form-inline" data-bind="if: printer_bedCircular">
|
||||
<label>Radius:</label>
|
||||
<label>{{ _('Radius') }}:</label>
|
||||
<div class="input-append">
|
||||
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: printer_bedDimensionR" id="settings-bedR">
|
||||
<span class="add-on">mm</span>
|
||||
|
|
@ -193,7 +193,7 @@
|
|||
</div>
|
||||
<div class="controls form-inline">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: printer_bedCircular" id="settings-bedCircular">Circular
|
||||
<input type="checkbox" data-bind="checked: printer_bedCircular" id="settings-bedCircular"> {{ _('Circular') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -202,25 +202,25 @@
|
|||
<div class="tab-pane" id="settings_webcam">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-webcamStreamUrl">Stream URL</label>
|
||||
<label class="control-label" for="settings-webcamStreamUrl">{{ _('Stream URL') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: webcam_streamUrl" id="settings-webcamStreamUrl">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-webcamStreamUrl">Snapshot URL</label>
|
||||
<label class="control-label" for="settings-webcamStreamUrl">{{ _('Snapshot URL') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: webcam_snapshotUrl" id="settings-webcamSnapshotUrl">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-webcamStreamUrl">Path to FFMPEG</label>
|
||||
<label class="control-label" for="settings-webcamStreamUrl">{{ _('Path to FFMPEG') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: webcam_ffmpegPath" id="settings-webcamFfmpegPath">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-webcamBitrate">Timelapse bitrate</label>
|
||||
<label class="control-label" for="settings-webcamBitrate">{{ _('Timelapse bitrate') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: webcam_bitrate" id="settings-webcamBitrate">
|
||||
</div>
|
||||
|
|
@ -228,19 +228,19 @@
|
|||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: webcam_watermark" id="settings-webcamWatermark"> Enable OctoPrint watermark in timelapse movies
|
||||
<input type="checkbox" data-bind="checked: webcam_watermark" id="settings-webcamWatermark"> {{ _('Enable OctoPrint watermark in timelapse movies') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: webcam_flipH" id="settings-webcamFlipH"> Flip webcam horizontally
|
||||
<input type="checkbox" data-bind="checked: webcam_flipH" id="settings-webcamFlipH"> {{ _('Flip webcam horizontally') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: webcam_flipV" id="settings-webcamFlipV"> Flip webcam vertically
|
||||
<input type="checkbox" data-bind="checked: webcam_flipV" id="settings-webcamFlipV"> {{ _('Flip webcam vertically') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -251,56 +251,56 @@
|
|||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_temperatureGraph" id="settings-featureTemperatureGraph"> Enable Temperature Graph
|
||||
<input type="checkbox" data-bind="checked: feature_temperatureGraph" id="settings-featureTemperatureGraph"> {{ _('Enable Temperature Graph') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_gcodeViewer" id="settings-featureGcodeViewer"> Enable GCode Visualizer
|
||||
<input type="checkbox" data-bind="checked: feature_gcodeViewer" id="settings-featureGcodeViewer"> {{ _('Enable GCode Visualizer') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_sdSupport" id="settings-featureSdSupport"> Enable SD support
|
||||
<input type="checkbox" data-bind="checked: feature_sdSupport" id="settings-featureSdSupport"> {{ _('Enable SD support') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_sdAlwaysAvailable" id="settings-featureSdAlwaysAvailable"> Always assume SD card is present <span class="label">Repetier</span>
|
||||
<input type="checkbox" data-bind="checked: feature_sdAlwaysAvailable" id="settings-featureSdAlwaysAvailable"> {{ _('Always assume SD card is present') }} <span class="label">{{ _('Repetier') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_waitForStart" id="settings-featureWaitForStart"> Wait for <code>start</code> on connect
|
||||
<input type="checkbox" data-bind="checked: feature_waitForStart" id="settings-featureWaitForStart"> {{ _('Wait for <code>start</code> on connect') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_alwaysSendChecksum" id="settings-featureAlwaysSendChecksum"> Send a checksum with <strong>every</strong> command <span class="label">Repetier</span>
|
||||
<input type="checkbox" data-bind="checked: feature_alwaysSendChecksum" id="settings-featureAlwaysSendChecksum"> {{ _('Send a checksum with <strong>every</strong> command') }} <span class="label">{{ _('Repetier') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_repetierTargetTemp" id="settings-featureRepetierTargetTemp"> Support <code>TargetExtr%n</code>/<code>TargetBed</code> target temperature format <span class="label">Repetier</span>
|
||||
<input type="checkbox" data-bind="checked: feature_repetierTargetTemp" id="settings-featureRepetierTargetTemp"> {{ _('Support <code>TargetExtr%%n</code>/<code>TargetBed</code> target temperature format') }} <span class="label">{{ _('Repetier') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" data-bind="checked: feature_swallowOkAfterResend" id="settings-swallowOkAfterResend"> Swallow the first "ok" after a resend response
|
||||
<input type="checkbox" data-bind="checked: feature_swallowOkAfterResend" id="settings-swallowOkAfterResend"> {{ _('Swallow the first "ok" after a resend response') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -309,31 +309,31 @@
|
|||
<div class="tab-pane" id="settings_folder">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-folderUploads">Upload Folder</label>
|
||||
<label class="control-label" for="settings-folderUploads">{{ _('Upload Folder') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: folder_uploads" id="settings-folderUploads">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-folderTimelapse">Timelapse Folder</label>
|
||||
<label class="control-label" for="settings-folderTimelapse">{{ _('Timelapse Folder') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: folder_timelapse" id="settings-folderTimelapse">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-folderTimelapseTemp">Timelapse Temp Folder</label>
|
||||
<label class="control-label" for="settings-folderTimelapseTemp">{{ _('Timelapse Temp Folder') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: folder_timelapseTmp" id="settings-folderTimelapseTemp">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-folderLogs">Logs Folder</label>
|
||||
<label class="control-label" for="settings-folderLogs">{{ _('Logs Folder') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: folder_logs" id="settings-folderLogs">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-watchedLogs">Watched Folder</label>
|
||||
<label class="control-label" for="settings-watchedLogs">{{ _('Watched Folder') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: folder_watched" id="settings-folderWatched">
|
||||
</div>
|
||||
|
|
@ -343,8 +343,8 @@
|
|||
<div class="tab-pane" id="settings_temperature">
|
||||
<form class="form-horizontal">
|
||||
<div class="row-fluid">
|
||||
<div class="offset3 span3"><h4>Extruder</h4></div>
|
||||
<div class="span3"><h4>Bed</h4></div>
|
||||
<div class="offset3 span3"><h4>{{ _('Extruder') }}</h4></div>
|
||||
<div class="span3"><h4>{{ _('Bed') }}</h4></div>
|
||||
</div>
|
||||
<div data-bind="foreach: temperature_profiles">
|
||||
<div class="row-fluid" style="margin-bottom: 5px">
|
||||
|
|
@ -374,8 +374,8 @@
|
|||
<div class="tab-pane" id="settings_terminalFilters">
|
||||
<form class="form-horizontal">
|
||||
<div class="row-fluid">
|
||||
<div class="span4"><h4>Name</h4></div>
|
||||
<div class="span6"><h4>RegExp <small><a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions">?</a></small></h4></div>
|
||||
<div class="span4"><h4>{{ _('Name') }}</h4></div>
|
||||
<div class="span6"><h4>{{ _('RegExp') }} <small><a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions">?</a></small></h4></div>
|
||||
</div>
|
||||
<div data-bind="foreach: terminalFilters">
|
||||
<div class="row-fluid" style="margin-bottom: 5px">
|
||||
|
|
@ -400,15 +400,15 @@
|
|||
<div class="tab-pane" id="settings_appearance">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-appearanceName">Title</label>
|
||||
<label class="control-label" for="settings-appearanceName">{{ _('Title') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: appearance_name" id="settings-appearanceName">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-appearanceColor">Color</label>
|
||||
<label class="control-label" for="settings-appearanceColor">{{ _('Color') }}</label>
|
||||
<div class="controls">
|
||||
<select id="settings-appearanceColor" data-bind="value: appearance_color, options: appearance_available_colors">
|
||||
<select id="settings-appearanceColor" data-bind="value: appearance_color, options: appearance_available_colors, optionsText: 'name', optionsValue: 'key'">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -417,19 +417,19 @@
|
|||
<div class="tab-pane" id="settings_cura">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-curaEnabled">Enable slicing via Cura</label>
|
||||
<label class="control-label" for="settings-curaEnabled">{{ _('Enable slicing via Cura') }}</label>
|
||||
<div class="controls">
|
||||
<input type="checkbox" data-bind="checked: cura_enabled">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-curaPath">Path to Cura</label>
|
||||
<label class="control-label" for="settings-curaPath">{{ _('Path to Cura') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: cura_path">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-curaConfig">Path to Cura config</label>
|
||||
<label class="control-label" for="settings-curaConfig">{{ _('Path to Cura config') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: cura_config">
|
||||
</div>
|
||||
|
|
@ -441,19 +441,19 @@
|
|||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="settings-apiEnabled" data-bind="checked: api_enabled"> Enable
|
||||
<input type="checkbox" id="settings-apiEnabled" data-bind="checked: api_enabled"> {{ _('Enable') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="settings-apiCors" data-bind="checked: api_allowCrossOrigin"> Allow <a href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing">Cross Origin Resource Sharing (CORS)</a>
|
||||
<input type="checkbox" id="settings-apiCors" data-bind="checked: api_allowCrossOrigin"> {{ _('Allow <a href="%(url)s">Cross Origin Resource Sharing (CORS)</a>', url = "https://en.wikipedia.org/wiki/Cross-origin_resource_sharing") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-apiKey">Apikey</label>
|
||||
<label class="control-label" for="settings-apiKey">{{ _('API Key') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" data-bind="value: api_key" id="settings-apikey">
|
||||
</div>
|
||||
|
|
@ -466,19 +466,19 @@
|
|||
<table class="table table-condensed table-hover" id="system_users">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="settings_users_name">Name</th>
|
||||
<th class="settings_users_active">Active</th>
|
||||
<th class="settings_users_admin">Admin</th>
|
||||
<th class="settings_users_actions">Action</th>
|
||||
<th class="settings_users_name">{{ _('Name') }}</th>
|
||||
<th class="settings_users_active">{{ _('Active') }}</th>
|
||||
<th class="settings_users_admin">{{ _('Admin') }}</th>
|
||||
<th class="settings_users_actions">{{ _('Action') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-bind="foreach: users.listHelper.paginatedItems">
|
||||
<tr>
|
||||
<td class="settings_users_name"><span data-bind="text: name"></span><span class="muted" data-bind="visible: $root.api_enabled() && apikey"><br /><small>Apikey: <span data-bind="text: apikey"></span></small></span></td>
|
||||
<td class="settings_users_name"><span data-bind="text: name"></span><span class="muted" data-bind="visible: $root.api_enabled() && apikey"><br /><small>{{ _('API Key') }}: <span data-bind="text: apikey"></span></small></span></td>
|
||||
<td class="settings_users_active"><i data-bind="css: { 'icon-check': active, 'icon-check-empty': !active }"></i></td>
|
||||
<td class="settings_users_admin"><i data-bind="css: { 'icon-check': admin, 'icon-check-empty': !admin }"></i></td>
|
||||
<td class="settings_users_actions" class="system_users_action">
|
||||
<a href="#" class="icon-pencil" title="Update User" data-bind="click: function() { $root.users.showEditUserDialog($data); }"></a> | <a href="#" class="icon-key" title="Change password" data-bind="click: function() { $root.users.showChangePasswordDialog($data); }"></a> | <a href="#" class="icon-trash" title="Delete user" data-bind="click: function() { $root.users.removeUser($data); }"></a>
|
||||
<a href="#" class="icon-pencil" title="{{ _('Update User') }}" data-bind="click: function() { $root.users.showEditUserDialog($data); }"></a> | <a href="#" class="icon-key" title="{{ _('Change password') }}" data-bind="click: function() { $root.users.showChangePasswordDialog($data); }"></a> | <a href="#" class="icon-trash" title="{{ _('Delete user') }}" data-bind="click: function() { $root.users.removeUser($data); }"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
@ -495,114 +495,114 @@
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<button title="Add user" class="btn" data-bind="click: $root.users.showAddUserDialog"><i class="icon-plus"></i> Create new user</button>
|
||||
<button title="Add user" class="btn" data-bind="click: $root.users.showAddUserDialog"><i class="icon-plus"></i> {{ _('Add user') }}</button>
|
||||
|
||||
<!-- Modals for user management -->
|
||||
|
||||
<div id="settings-usersDialogAddUser" class="modal hide fade">
|
||||
<div class="modal-header">
|
||||
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">×</a>
|
||||
<h3>Create new user</h3>
|
||||
<h3>{{ _('Add user') }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-usersDialogAddUserName">Username</label>
|
||||
<label class="control-label" for="settings-usersDialogAddUserName">{{ _('Username') }}</label>
|
||||
<div class="controls">
|
||||
<input type="text" class="input-block-level" id="settings-usersDialogAddUserName" data-bind="value: $root.users.editorUsername" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-usersDialogAddUserPassword1">Password</label>
|
||||
<label class="control-label" for="settings-usersDialogAddUserPassword1">{{ _('Password') }}</label>
|
||||
<div class="controls">
|
||||
<input type="password" class="input-block-level" id="settings-usersDialogAddUserPassword1" data-bind="value: $root.users.editorPassword" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" data-bind="css: {error: $root.users.editorPasswordMismatch()}">
|
||||
<label class="control-label" for="settings-usersDialogAddUserPassword2">Repeat Password</label>
|
||||
<label class="control-label" for="settings-usersDialogAddUserPassword2">{{ _('Repeat Password') }}</label>
|
||||
<div class="controls">
|
||||
<input type="password" class="input-block-level" id="settings-usersDialogAddUserPassword2" data-bind="value: $root.users.editorRepeatedPassword, valueUpdate: 'afterkeydown'" required>
|
||||
<span class="help-inline" data-bind="visible: $root.users.editorPasswordMismatch()">Passwords do not match</span>
|
||||
<span class="help-inline" data-bind="visible: $root.users.editorPasswordMismatch()">{{ _('Passwords do not match') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="settings-usersDialogAddUserActive" data-bind="checked: $root.users.editorActive"> Active
|
||||
<input type="checkbox" id="settings-usersDialogAddUserActive" data-bind="checked: $root.users.editorActive"> {{ _('Active') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="settings-usersDialogAddUserAdmin" data-bind="checked: $root.users.editorAdmin"> Admin
|
||||
<input type="checkbox" id="settings-usersDialogAddUserAdmin" data-bind="checked: $root.users.editorAdmin"> {{ _('Admin') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">Abort</button>
|
||||
<button class="btn btn-primary" data-bind="click: function() { $root.users.confirmAddUser(); }, enable: !$root.users.editorPasswordMismatch()">Confirm</button>
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Abort') }}</button>
|
||||
<button class="btn btn-primary" data-bind="click: function() { $root.users.confirmAddUser(); }, enable: !$root.users.editorPasswordMismatch()">{{ _('Confirm') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="settings-usersDialogEditUser" class="modal hide fade">
|
||||
<div class="modal-header">
|
||||
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">×</a>
|
||||
<h3>Edit user "<span data-bind="text: $root.users.editorUsername"></span>"</h3>
|
||||
<h3>{{ _('Edit user "%(user)s"', user = '<span data-bind="text: $root.users.editorUsername"></span>') }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="settings-usersDialogEditUserActive" data-bind="checked: $root.users.editorActive"> Active
|
||||
<input type="checkbox" id="settings-usersDialogEditUserActive" data-bind="checked: $root.users.editorActive"> {{ _('Active') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="settings-usersDialogEditUserAdmin" data-bind="checked: $root.users.editorAdmin"> Admin
|
||||
<input type="checkbox" id="settings-usersDialogEditUserAdmin" data-bind="checked: $root.users.editorAdmin"> {{ _('Admin') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">Abort</button>
|
||||
<button class="btn btn-primary" data-bind="click: function() { $root.users.confirmEditUser(); }">Confirm</button>
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Abort') }}</button>
|
||||
<button class="btn btn-primary" data-bind="click: function() { $root.users.confirmEditUser(); }">{{ _('Confirm') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="settings-usersDialogChangePassword" class="modal hide fade">
|
||||
<div class="modal-header">
|
||||
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">×</a>
|
||||
<h3>Change password for user "<span data-bind="text: $root.users.editorUsername"></span>"</h3>
|
||||
<h3>{{ _('Change password for user "%(user)s"', user='<span data-bind="text: $root.users.editorUsername"></span>') }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="settings-usersDialogChangePasswordPassword1">New Password</label>
|
||||
<label class="control-label" for="settings-usersDialogChangePasswordPassword1">{{ _('New Password') }}</label>
|
||||
<div class="controls">
|
||||
<input type="password" class="input-block-level" id="settings-usersDialogChangePasswordPassword1" data-bind="value: $root.users.editorPassword" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" data-bind="css: {error: $root.users.editorPasswordMismatch()}">
|
||||
<label class="control-label" for="settings-usersDialogChangePasswordPassword2">Repeat Password</label>
|
||||
<label class="control-label" for="settings-usersDialogChangePasswordPassword2">{{ _('Repeat Password') }}</label>
|
||||
<div class="controls">
|
||||
<input type="password" class="input-block-level" id="settings-usersDialogChangePasswordPassword2" data-bind="value: $root.users.editorRepeatedPassword, valueUpdate: 'afterkeydown'" required>
|
||||
<span class="help-inline" data-bind="visible: $root.users.editorPasswordMismatch()">Passwords do not match</span>
|
||||
<span class="help-inline" data-bind="visible: $root.users.editorPasswordMismatch()">{{ _('Passwords do not match') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset data-bind="visible: api_enabled">
|
||||
<legend>Apikey</legend>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Current Apikey</label>
|
||||
<label class="control-label">{{ _('Current API Key') }}</label>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<input type="text" class="input-block-level uneditable-input" data-bind="value: $root.users.editorApikey, attr: {placeholder: 'N/A'}">
|
||||
<input type="text" class="input-block-level uneditable-input" data-bind="value: $root.users.editorApikey, attr: {placeholder: '{{ _('N/A') }}'}">
|
||||
<a class="btn" title="Generate new Apikey" data-bind="click: function() { $root.users.confirmGenerateApikey(); }"><i class="icon-refresh"></i></a>
|
||||
<a class="btn btn-danger" title="Delete Apikey" data-bind="click: function() { $root.users.confirmDeleteApikey(); }"><i class="icon-trash"></i></a>
|
||||
</div>
|
||||
|
|
@ -613,8 +613,8 @@
|
|||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">Abort</button>
|
||||
<button class="btn btn-primary" data-bind="click: function() { $root.users.confirmChangePassword(); }, enable: !$root.users.editorPasswordMismatch()">Confirm</button>
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Abort') }}</button>
|
||||
<button class="btn btn-primary" data-bind="click: function() { $root.users.confirmChangePassword(); }, enable: !$root.users.editorPasswordMismatch()">{{ _('Confirm') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -623,20 +623,20 @@
|
|||
|
||||
<div class="tab-pane" id="settings_logs" data-bind="allowBindings: false">
|
||||
<div id="logs">
|
||||
<h1>Logs</h1>
|
||||
<h1>{{ _('Logs') }}</h1>
|
||||
|
||||
<div class="pull-right">
|
||||
<small>
|
||||
Sort by: <a href="#" data-bind="click: function() { listHelper.changeSorting('name'); }">Name (ascending)</a> | <a href="#" data-bind="click: function() { listHelper.changeSorting('modification'); }">Modification date (descending)</a> | <a href="#" data-bind="click: function() { listHelper.changeSorting('size'); }">Size (descending)</a>
|
||||
{{ _('Sort by') }}: <a href="#" data-bind="click: function() { listHelper.changeSorting('name'); }">{{ _('Name') }} ({{ _('ascending') }})</a> | <a href="#" data-bind="click: function() { listHelper.changeSorting('modification'); }">{{ _('Modification date') }} ({{ _('descending') }})</a> | <a href="#" data-bind="click: function() { listHelper.changeSorting('size'); }">{{ _('Size') }} ({{ _('descending') }})</a>
|
||||
</small>
|
||||
</div>
|
||||
<table class="table table-striped table-hover table-condensed table-hover" id="log_files">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="settings_logs_name">Name</th>
|
||||
<th class="settings_logs_size">Size</th>
|
||||
<th class="settings_logs_date">Date</th>
|
||||
<th class="settings_logs_action">Action</th>
|
||||
<th class="settings_logs_name">{{ _('Name') }}</th>
|
||||
<th class="settings_logs_size">{{ _('Size') }}</th>
|
||||
<th class="settings_logs_date">{{ _('Date') }}</th>
|
||||
<th class="settings_logs_action">{{ _('Action') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-bind="foreach: listHelper.paginatedItems">
|
||||
|
|
@ -673,7 +673,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||
<button class="btn btn-primary" data-bind="click: saveData">Save</button>
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Cancel') }}</button>
|
||||
<button class="btn btn-primary" data-bind="click: saveData">{{ _('Save') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
BIN
src/octoprint/translations/de/LC_MESSAGES/messages.mo
Normal file
BIN
src/octoprint/translations/de/LC_MESSAGES/messages.mo
Normal file
Binary file not shown.
1310
src/octoprint/translations/de/LC_MESSAGES/messages.po
Normal file
1310
src/octoprint/translations/de/LC_MESSAGES/messages.po
Normal file
File diff suppressed because it is too large
Load diff
1242
src/octoprint/translations/messages.pot
Normal file
1242
src/octoprint/translations/messages.pot
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue