update to localization.

* keep Slic3r in tooltip, as it's replaced
* use %S or %1% to set the name of the software in other places.
* pom_merger now don't save empty translations.
* pom_merger now can propose similar but not identical translations, to be able to copy & modify them
* pom_merger now can add language to header via a setting.
* github actions will now compile the pot each time. No need to store it in github
This commit is contained in:
remi durand 2021-06-09 11:53:21 +02:00
parent a758dd8913
commit 14e6ed3e56
15 changed files with 199 additions and 14484 deletions

View File

@ -52,6 +52,9 @@ jobs:
- name: make .mo
working-directory: ./build
run: make gettext_po_to_mo
- name: make .pot
working-directory: ./build
run: make gettext_make_pot
- name: update Info.plist
working-directory: ./build/src
run: sed "s/+UNKNOWN/_$(date '+%F')/" Info.plist >Info.date.plist

View File

@ -52,6 +52,9 @@ jobs:
- name: make .mo
working-directory: ./build
run: make gettext_po_to_mo
- name: make .pot
working-directory: ./build
run: make gettext_make_pot
- name: update Info.plist
working-directory: ./build/src
run: sed "s/+UNKNOWN/_$(date '+%F')/" Info.plist >Info.date.plist

View File

@ -53,6 +53,9 @@ jobs:
- name: make .mo
working-directory: ./build
run: make gettext_po_to_mo
- name: make .pot
working-directory: ./build
run: make gettext_make_pot
- name: create directory and copy into it
working-directory: ./build
run: |

View File

@ -53,6 +53,9 @@ jobs:
- name: make .mo
working-directory: ./build
run: make gettext_po_to_mo
- name: make .pot
working-directory: ./build
run: make gettext_make_pot
- name: create directory and copy into it
working-directory: ./build
run: |

View File

@ -62,6 +62,9 @@ jobs:
- name: make .mo
working-directory: ./build
run: msbuild /m /P:Configuration=Release gettext_po_to_mo.vcxproj
- name: make .pot
working-directory: ./build
run: msbuild /m /P:Configuration=Release gettext_make_pot.vcxproj
- name: create directory and copy into it
working-directory: ./build
run: ls

View File

@ -62,6 +62,9 @@ jobs:
- name: make .mo
working-directory: ./build
run: msbuild /m /P:Configuration=Release gettext_po_to_mo.vcxproj
- name: make .pot
working-directory: ./build
run: msbuild /m /P:Configuration=Release gettext_make_pot.vcxproj
- name: create directory and copy into it
working-directory: ./build
run: ls

View File

@ -38,12 +38,20 @@ To decompile the .mo of Prusaslicer, use the command `msgunfmt PrusaSlicer.mo -o
So the settings.ini contains these lines :
```
data = es/my_old_po_file.po
data = es/Slic3r.po
data = MyKnowledgeBase.po
data = es/PrusaSlicer_es.po
database_out = MyKnowledgeBase.po
ui_dir = ../ui_layout
allow_msgctxt = false
ignore_case = false
remove_comment = true
percent_error_similar = 0.4
max_similar = 3
language = french
language_code = fr
input = Slic3r.pot
todo = es/todo.po
@ -51,10 +59,15 @@ output = es/Slic3r.po
```
Notes:
* thee 'todo' and 'output' files will be erased, so be sure nothing has this name (or write another name)
* thee 'todo' and 'output' files will be erased, so be sure nothing important has this name (or write another name)
* the file at 'database_out' will receive all the database created from the data files. That way, it will keep your new & old unused translations just in case the wording revert back to it, or to be used as reference for the helper.
* ui_dir should be the path to the slic3r/resources/ui_layout directory. If you're in slic3r/resources/localization, this value is good.
* allow_msgctxt is a bool to allow to keep the msgctxt tags. You need a recent version of gettext to use that.
* ignore_case is a bool that will let the tool to ignore the case when comparing msgid if no translation is found.
* remove_comment is a bool taht will remove all comment in the output file. It's to avoid unecessary changes in the git commit.
* percent_error_similar is a number between 0 and 1. This will activate the helper that will write help comment in the TODO file. These will present similar string that are already translated, to let you pick chunk that are already translated to avoid redoing all the work. It's the percentage of difference allowed (0 = identical, 1 = everything, 0.5 = not more than half of the string is different), using (levenshtein distance / msgid length).
* max_similar: max number of help translation per item
* language and language_code: text to include in the header.
### 2) launch the utility.
@ -83,13 +96,18 @@ After filling the todo file, change the settings.ini:
```
data = es/todo.po
data = es/Slic3r.po
data = es/my_old_po_file.po
data = es/PrusaSlicer_es.po
data = MyKnowledgeBase.po
database_out = MyKnowledgeBase.po
ui_dir = ../ui_layout
allow_msgctxt = false
ignore_case = false
remove_comment = true
percent_error_similar = 0.4
max_similar = 3
language = french
language_code = fr
input = Slic3r.pot
todo = es/todo.po

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,13 @@
import re
from datetime import date
try:
from Levenshtein import distance as levenshtein_distance
except ImportError:
print("you need to do 'python -m pip install python-Levenshtein'");
exit(0);
datastore = dict();
datastore_trim = dict();# key:string -> TranslationLine
@ -8,6 +15,11 @@ regex_only_letters = re.compile(r"[^a-zA-Z]")
allow_msgctxt = True;
ignore_case = False;
remove_comment = False;
percent_error_similar = 0
language_code = "??"
language = ""
max_similar = 3;
database_out = "";
def trim(str):
redo = True;
@ -37,6 +49,7 @@ class TranslationFiles:
file_in = ""
file_out = ""
file_todo = ""
database = ""
class TranslationLine:
header_comment = ""
@ -48,6 +61,7 @@ class TranslationLine:
def main():
global datastore, datastore_trim, regex_only_letters, allow_msgctxt, ignore_case, remove_comment;
global percent_error_similar, language, language_code, max_similar, database_out;
data_files = list(); # list of file paths
ui_dir = "";
operations = list(); # list of TranslationFiles
@ -55,6 +69,9 @@ def main():
lines = settings_stream.read().splitlines()
for line in lines:
if line.startswith("data"):
if line.startswith("database_out"):
database_out = line[line.index('=')+1:].strip();
else:
data_files.append(line[line.index('=')+1:].strip());
if line.startswith("input"):
@ -67,6 +84,7 @@ def main():
if line.startswith("todo") and operations:
operations[-1].file_todo = line[line.index('=')+1:].strip();
if line.startswith("ui_dir"):
ui_dir = line[line.index('=')+1:].strip();
@ -83,11 +101,29 @@ def main():
if remove_comment:
print("Will not output the comments");
if line.startswith("percent_error_similar"):
percent_error_similar = float(line[line.index('=')+1:].strip());
print("percent_error_similar set to " + str(percent_error_similar));
if line.startswith("max_similar"):
max_similar = int(line[line.index('=')+1:].strip());
print("max_similar set to " + str(max_similar));
if line.startswith("language"):
if line.startswith("language_code"):
language_code = line[line.index('=')+1:].strip();
print("language_code set to " + language_code);
else:
language = line[line.index('=')+1:].strip();
print("language set to " + language);
# all_lines = list();
for data_file in data_files:
new_data = createKnowledge(data_file);
for dataline in new_data:
if len(dataline.msgstr) > 0:
if not dataline.msgid in datastore:
datastore[dataline.msgid] = dataline;
datastore_trim[trim(dataline.msgid)] = dataline;
@ -156,6 +192,10 @@ def main():
ope_file_in.append(dataline);
print("String to translate: " + str(len(ope_file_in) - nbTrans)+" and already translated: "+str(nbTrans));
#create database
if database_out:
outputDatabase(database_out);
#create TODO file
if operation.file_todo:
outputUntranslated(ope_file_in, operation.file_todo);
@ -260,7 +300,7 @@ def createKnowledge(file_path_in):
read_data_lines.append(current_line);
current_line = TranslationLine();
except Exception as error:
print("error, cannot read file " + file_path_in);
print("Warning, cannot read file " + file_path_in);
print(error);
return read_data_lines;
@ -301,12 +341,21 @@ def getTranslation(item):
return getTranslation(lowercase)
return "";
def getTranslationNear(msgid_to_search, percent):
max_word_diff = 1 + int(percent * len(msgid_to_search));
possible_solutions = list();
for msgid in datastore:
dist = levenshtein_distance(msgid, msgid_to_search);
if dist < max_word_diff:
possible_solutions.append( (dist, datastore[msgid]) );
possible_solutions.sort(key=lambda x:x[0]);
return possible_solutions;
def outputUntranslated(data_to_translate, file_path_out):
try:
file_out_stream = open(file_path_out, mode="w", encoding="utf-8")
nb_lines = 0;
#sort to have an easier time trnaslating.
#sort to have an easier time translating.
# idealy, they shoud be grouped by proximity, but it's abit more complicated to code
sorted_lines = list()
for dataline in data_to_translate:
@ -316,12 +365,20 @@ def outputUntranslated(data_to_translate, file_path_out):
# output bits that are empty
for dataline in sorted_lines:
file_out_stream.write(dataline.header_comment)
file_out_stream.write("\n")
file_out_stream.write(dataline.raw_msgid)
file_out_stream.write("\n")
file_out_stream.write(dataline.raw_msgstr)
file_out_stream.write("\n")
file_out_stream.write(dataline.header_comment);
file_out_stream.write("\n");
# get translation that are near enough to be copy-pasted by humans.
good_enough = getTranslationNear(dataline.msgid, 0.4);
if len(good_enough) >0:
file_out_stream.write("#Similar to me: "+dataline.msgid+"\n");
for index in range(min(len(good_enough), max_similar)):
file_out_stream.write("# "+str(good_enough[index][0])+("" if len(str(good_enough[index][0]))>2 else " " if len(str(good_enough[index][0]))==2 else " ")
+" changes: " + good_enough[index][1].msgid+"\n");
file_out_stream.write("# translation: " + good_enough[index][1].msgstr+"\n");
file_out_stream.write(dataline.raw_msgid);
file_out_stream.write("\n");
file_out_stream.write(dataline.raw_msgstr);
file_out_stream.write("\n");
nb_lines+=1;
print("There is " + str(nb_lines) +" string untranslated");
@ -332,7 +389,7 @@ def outputUntranslated(data_to_translate, file_path_out):
def translate(data_to_translate, file_path_out):
# try:
file_out_stream = open(file_path_out, mode="w", encoding="utf-8")
file_out_stream.write("# Translation file for ???\n");
file_out_stream.write("# Translation file for "+(language if len(language)>0 else language_code)+"\n");
file_out_stream.write("# Copyright (C) 2021\n");
file_out_stream.write("# This file is distributed under the same license as Slic3r.\n");
file_out_stream.write("#\n");
@ -346,7 +403,7 @@ def translate(data_to_translate, file_path_out):
file_out_stream.write("\"MIME-Version: 1.0\\n\"\n");
file_out_stream.write("\"Content-Type: text/plain; charset=UTF-8\\n\"\n");
file_out_stream.write("\"Content-Transfer-Encoding: 8bit\\n\"\n");
file_out_stream.write("\"Language:\\n\"\n");
file_out_stream.write("\"Language:"+language_code+"\\n\"\n");
nb_lines = 0;
data_to_translate.sort(key=lambda x:x.msgid.lower().strip())
# translate bits that are empty
@ -384,6 +441,26 @@ def translate(data_to_translate, file_path_out):
# print("error, cannot write file " + file_path_out);
# print(error);
def outputDatabase(file_path_out):
try:
file_out_stream = open(file_path_out, mode="w", encoding="utf-8")
nb_lines = 0;
for msgid in datastore:
dataline = datastore[msgid];
file_out_stream.write(dataline.header_comment);
file_out_stream.write("\n");
file_out_stream.write(dataline.raw_msgid);
file_out_stream.write("\n");
file_out_stream.write(dataline.raw_msgstr);
file_out_stream.write("\n");
nb_lines+=1;
print("There is " + str(nb_lines) +" in your database file");
except Exception as error:
print("error, cannot write file " + file_path_out);
print(error);
def parse_ui_file(file_path):
read_data_lines = list();
# try:
@ -414,7 +491,7 @@ def parse_ui_file(file_path):
if items[0]=="setting":
for item in items:
if item.startswith("label$") or item.startswith("full_label$") or item.startswith("sidetext$") or item.startswith("tooltip$"):
if item.split("$")[-1] != "_" and len(item.split("$")[-1]) > 0 :
if item.split("$")[-1] != '_' and len(item.split("$")[-1]) > 0 :
current_line = TranslationLine();
current_line.header_comment = "\n#: "+file_path+" : l"+str(line_idx);
current_line.msgid = item.split("$")[-1];

Binary file not shown.

View File

@ -1,15 +1,35 @@
# all files taken as input to construt the knowledge base
# a file can't overwrite what's already inside (unless it's empty), so put the best files in first.
# if a file ins't here, a warning message will be emmited
data = Slic3r.po
data = TODO.po
data = MyKnowledgeBase.po
#data = C:/local/Slic3r/resources/localization/lang/TODO.po
#data = C:/local/Slic3r/resources/localization/lang/Slic3r.po
#data = Slic3r++.po
data = it/TODO.po
data = it/Slic3r.po
# optional: output all the knowledge base into a file, to be reused in the future.
database_out = MyKnowledgeBase.po
# path to the ui_layout dir, to grab all ui string defined here
ui_dir = ../ui_layout
# to allow to keep the msgctxt tags. You need a recent version of gettext to use that.
allow_msgctxt = false
# the tool to ignore the case when comparing msgid if no exact translation is found.
ignore_case = false
# will remove the comments in the output files (not the todo).
remove_comment = true
# flaot between 0 and 1. If higher than 0, the tool may porpose you some similar transaltion from the knowledge base in the comment
# to help you write the translation. It's useful when only a part of the original string changes, so you can reuse almost everything.
# it's the % of diff (levenshtein distance / msgid length) allowed for an other string to be proposed
percent_error_similar = 0.4
# max number of proposed translation per item. Work with percent_error_similar.
max_similar = 3
# strings written in the header
language = french
language_code = fr
# input is the pot (or po) where the msgid are picked
input = Slic3r.pot
todo = it/TODO.po
output = it/Slic3r.po
# the todo will receive the msgid for which no translation are found
todo = fr/TODO.po
# the output will receive the other msgid with their translation
output = fr/Slic3r.po

View File

@ -1754,8 +1754,8 @@ void PrintConfigDef::init_fff_params()
"You can use this to force fatter extrudates for better adhesion. If expressed "
"as percentage (for example 140%) it will be computed over the nozzle diameter "
"of the nozzle used for the type of extrusion. "
"If set to zero, it will use the default extrusion width."
"\nYou can set either 'Spacing', or 'Width'; the other will be calculated, using the perimeter 'Overlap' percentages and default layer height.");
"If set to zero, it will use the default extrusion width.") + std::string("\n") +
L("You can set either 'Spacing', or 'Width'; the other will be calculated, using the perimeter 'Overlap' percentages and default layer height.");
def->sidetext = L("mm or %");
def->ratio_over = "nozzle_diameter";
def->min = 0;
@ -1992,8 +1992,7 @@ void PrintConfigDef::init_fff_params()
def->label = L("Length of the infill anchor");
def->category = OptionCategory::infill;
def->tooltip = L("Connect an infill line to an internal perimeter with a short segment of an additional perimeter. "
"If expressed as percentage (example: 15%) it is calculated over infill extrusion width. "
SLIC3R_APP_NAME " tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment "
"If expressed as percentage (example: 15%) it is calculated over infill extrusion width. Slic3r tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment "
"shorter than infill_anchor_max is found, the infill line is connected to a perimeter segment at just one side "
"and the length of the perimeter segment taken is limited to this parameter, but no longer than anchor_length_max. "
"\nSet this parameter to zero to disable anchoring perimeters connected to a single infill line.");
@ -2019,8 +2018,7 @@ void PrintConfigDef::init_fff_params()
def->label = L("Maximum length of the infill anchor");
def->category = def_infill_anchor_min->category;
def->tooltip = L("Connect an infill line to an internal perimeter with a short segment of an additional perimeter. "
"If expressed as percentage (example: 15%) it is calculated over infill extrusion width. "
SLIC3R_APP_NAME " tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment "
"If expressed as percentage (example: 15%) it is calculated over infill extrusion width. Slic3r tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment "
"shorter than this parameter is found, the infill line is connected to a perimeter segment at just one side "
"and the length of the perimeter segment taken is limited to infill_anchor, but no longer than this parameter. "
"\nIf set to 0, the old algorithm for infill connection will be used, it should create the same result as with 1000 & 0.");
@ -2873,7 +2871,7 @@ void PrintConfigDef::init_fff_params()
def->label = L("Round corners");
def->full_label = L("Round corners for perimeters");
def->category = OptionCategory::perimeter;
def->tooltip = L("Internal periemters will go around sharp corners by tunring around isntead of making the same sharp corner."
def->tooltip = L("Internal perimeters will go around sharp corners by turning around instead of making the same sharp corner."
" This can help when there is visible holes in sharp corners on perimeters");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
@ -3023,7 +3021,7 @@ void PrintConfigDef::init_fff_params()
"the slicing job and reducing memory usage. High-resolution models often carry "
"more details than printers can render. Set to zero to disable any simplification "
"and use full resolution from input. "
"\nNote: " SLIC3R_APP_NAME " has an internal working resolution of 0.0001mm."
"\nNote: Slic3r has an internal working resolution of 0.0001mm."
"\nInfill & Thin areas are simplified up to 0.0125mm.");
def->sidetext = L("mm");
def->min = 0;
@ -3915,7 +3913,7 @@ void PrintConfigDef::init_fff_params()
def->full_label = L("Thin walls");
def->category = OptionCategory::perimeter;
def->tooltip = L("Detect single-width walls (parts where two extrusions don't fit and we need "
"to collapse them into a single trace). If unchecked, slic3r may try to fit perimeters "
"to collapse them into a single trace). If unchecked, Slic3r may try to fit perimeters "
"where it's not possible, creating some overlap leading to over-extrusion.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(true));
@ -3926,7 +3924,7 @@ void PrintConfigDef::init_fff_params()
def->category = OptionCategory::perimeter;
def->tooltip = L("Minimum width for the extrusion to be extruded (widths lower than the nozzle diameter will be over-extruded at the nozzle diameter)."
" If expressed as percentage (for example 110%) it will be computed over nozzle diameter."
" The default behavior of slic3r and slic3rPE is with a 33% value. Put 100% to avoid any sort of over-extrusion.");
" The default behavior of PrusaSlicer is with a 33% value. Put 100% to avoid any sort of over-extrusion.");
def->ratio_over = "nozzle_diameter";
def->mode = comExpert;
def->min = 0;
@ -3976,7 +3974,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("time_estimation_compensation", coPercent);
def->label = L("Time estimation compensation");
def->category = OptionCategory::firmware;
def->tooltip = L("This setting allow you to modify the time estiamtion by a % amount. As slic3r only use the marlin algorithm, it's not precise enough if an other firmware is used.");
def->tooltip = L("This setting allow you to modify the time estiamtion by a % amount. As Slic3r only use the marlin algorithm, it's not precise enough if an other firmware is used.");
def->mode = comAdvanced;
def->sidetext = L("%");
def->min = 0;
@ -3986,10 +3984,10 @@ void PrintConfigDef::init_fff_params()
def->label = L("Tool change G-code");
def->category = OptionCategory::customgcode;
def->tooltip = L("This custom code is inserted at every extruder change. If you don't leave this empty, you are "
"expected to take care of the toolchange yourself - slic3r will not output any other G-code to "
"expected to take care of the toolchange yourself - Slic3r will not output any other G-code to "
"change the filament. You can use placeholder variables for all Slic3r settings as well as [previous_extruder] "
"and [next_extruder], so e.g. the standard toolchange command can be scripted as T[next_extruder]."
"!! Warning !!: if any charater is written here, slic3r won't output any toochange command by itself.");
"!! Warning !!: if any charater is written here, Slic3r won't output any toochange command by itself.");
def->multiline = true;
def->full_width = true;
def->height = 5;
@ -4167,7 +4165,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("wipe_advanced", coBool);
def->label = L("Enable advanced wiping volume");
def->tooltip = L("Allow slic3r to compute the purge volume via smart computations. Use the pigment% of each filament and following parameters");
def->tooltip = L("Allow Slic3r to compute the purge volume via smart computations. Use the pigment% of each filament and following parameters");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false));
@ -6504,8 +6502,8 @@ CLIMiscConfigDef::CLIMiscConfigDef()
def = this->add("single_instance", coBool);
def->label = L("Single instance mode");
def->tooltip = L("If enabled, the command line arguments are sent to an existing instance of GUI " SLIC3R_APP_NAME ", "
"or an existing " SLIC3R_APP_NAME " window is activated. "
def->tooltip = L("If enabled, the command line arguments are sent to an existing instance of GUI Slic3r, "
"or an existing Slic3r window is activated. "
"Overrides the \"single_instance\" configuration value from application preferences.");
/*

View File

@ -1141,15 +1141,15 @@ wxString Preview::get_option_type_string(OptionType type) const
{
case OptionType::Travel: { return _L("Travel"); }
case OptionType::Wipe: { return _L("Wipe"); }
case OptionType::Retractions: { return _L(m_width_screen == tiny ? "Retr." : "Retractions"); }
case OptionType::Unretractions: { return _L(m_width_screen == tiny ? "Dere." : "Deretractions"); }
case OptionType::ToolChanges: { return _L(m_width_screen == tiny ? "Tool/C" : "Tool changes"); }
case OptionType::ColorChanges: { return _L(m_width_screen == tiny ? "Col/C" : "Color changes"); }
case OptionType::PausePrints: { return _L(m_width_screen == tiny ? "Pause" : "Print pauses"); }
case OptionType::CustomGCodes: { return _L(m_width_screen == tiny ? "Custom" : "Custom G-codes"); }
case OptionType::Retractions: { return m_width_screen == tiny ? _L("Retr.") : _L("Retractions"); }
case OptionType::Unretractions: { return m_width_screen == tiny ? _L("Dere.") : _L("Deretractions"); }
case OptionType::ToolChanges: { return m_width_screen == tiny ? _L("Tool/C") : _L("Tool changes"); }
case OptionType::ColorChanges: { return m_width_screen == tiny ? _L("Col/C") : _L("Color changes"); }
case OptionType::PausePrints: { return m_width_screen == tiny ? _L("Pause") : _L("Print pauses"); }
case OptionType::CustomGCodes: { return m_width_screen == tiny ? _L("Custom") : _L("Custom G-codes"); }
case OptionType::Shells: { return _L("Shells"); }
case OptionType::ToolMarker: { return _L(m_width_screen == tiny ? "Marker" : "Tool marker"); }
case OptionType::Legend: { return _L(m_width_screen == tiny ? "Legend" : "Legend/Estimated printing time"); }
case OptionType::ToolMarker: { return m_width_screen == tiny ? _L("Marker") : _L("Tool marker"); }
case OptionType::Legend: { return m_width_screen == tiny ? _L("Legend") : _L("Legend/Estimated printing time"); }
default: { return ""; }
}
}

View File

@ -168,11 +168,11 @@ void PreferencesDialog::build()
#if __APPLE__
def.label = (L("Allow just a single Slic3r instance")) % SLIC3R_APP_NAME).str();
def.label = (boost::format(L("Allow just a single %1% instance")) % SLIC3R_APP_NAME).str();
def.type = coBool;
def.tooltip = L("On OSX there is always only one instance of app running by default. However it is allowed to run multiple instances of same app from the command line. In such case this settings will allow only one instance.");
#else
def.label = (boost::format(L("Allow just a single Slic3r instance")) % SLIC3R_APP_NAME).str();
def.label = (boost::format(L("Allow just a single %1% instance")) % SLIC3R_APP_NAME).str();
def.type = coBool;
def.tooltip = L("If this is enabled, when starting Slic3r and another instance of the same Slic3r is already running, that instance will be reactivated instead.");
#endif
@ -211,9 +211,9 @@ void PreferencesDialog::build()
#if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN
#ifdef _WIN32
else {
def.label = (boost::format(L("Associate .gcode files to Slic3r G-code Viewer")) % SLIC3R_APP_NAME).str();
def.label = (boost::format(L("Associate .gcode files to %1%")) % GCODEVIEWER_APP_NAME).str();
def.type = coBool;
def.tooltip = L("If enabled, sets Slic3r G-code Viewer as default application to open .gcode files.");
def.tooltip = (boost::format(L("If enabled, sets %1% as default application to open .gcode files.")) % GCODEVIEWER_APP_NAME).str();
def.set_default_value(new ConfigOptionBool(app_config->get("associate_gcode") == "1"));
option = Option(def, "associate_gcode");
m_optgroup_general->append_single_option_line(option);

View File

@ -178,7 +178,7 @@ const char* SL1Host::get_name() const { return "SL1Host"; }
wxString SL1Host::get_test_ok_msg () const
{
return _(L("Connection to Prusa SL1 works correctly."));
return wxString::Format(_L("Connection to %s works correctly."), "Prusa SL1");
}
wxString SL1Host::get_test_failed_msg (wxString &msg) const