Fix parameter passing to gettext call
[oweals/minetest.git] / builtin / mainmenu / tab_settings.lua
1 --Minetest
2 --Copyright (C) 2015 PilzAdam
3 --
4 --This program is free software; you can redistribute it and/or modify
5 --it under the terms of the GNU Lesser General Public License as published by
6 --the Free Software Foundation; either version 2.1 of the License, or
7 --(at your option) any later version.
8 --
9 --This program is distributed in the hope that it will be useful,
10 --but WITHOUT ANY WARRANTY; without even the implied warranty of
11 --MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 --GNU Lesser General Public License for more details.
13 --
14 --You should have received a copy of the GNU Lesser General Public License along
15 --with this program; if not, write to the Free Software Foundation, Inc.,
16 --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18 local FILENAME = "settingtypes.txt"
19
20 local CHAR_CLASSES = {
21         SPACE = "[%s]",
22         VARIABLE = "[%w_%-%.]",
23         INTEGER = "[-]?[%d]",
24         FLOAT = "[-]?[%d%.]",
25         FLAGS = "[%w_%-%.,]",
26 }
27
28 -- returns error message, or nil
29 local function parse_setting_line(settings, line, read_all, base_level, allow_secure)
30         -- comment
31         local comment = line:match("^#" .. CHAR_CLASSES.SPACE .. "*(.*)$")
32         if comment then
33                 if settings.current_comment == "" then
34                         settings.current_comment = comment
35                 else
36                         settings.current_comment = settings.current_comment .. "\n" .. comment
37                 end
38                 return
39         end
40
41         -- clear current_comment so only comments directly above a setting are bound to it
42         -- but keep a local reference to it for variables in the current line
43         local current_comment = settings.current_comment
44         settings.current_comment = ""
45
46         -- empty lines
47         if line:match("^" .. CHAR_CLASSES.SPACE .. "*$") then
48                 return
49         end
50
51         -- category
52         local stars, category = line:match("^%[([%*]*)([^%]]+)%]$")
53         if category then
54                 table.insert(settings, {
55                         name = category,
56                         level = stars:len() + base_level,
57                         type = "category",
58                 })
59                 return
60         end
61
62         -- settings
63         local first_part, name, readable_name, setting_type = line:match("^"
64                         -- this first capture group matches the whole first part,
65                         --  so we can later strip it from the rest of the line
66                         .. "("
67                                 .. "([" .. CHAR_CLASSES.VARIABLE .. "+)" -- variable name
68                                 .. CHAR_CLASSES.SPACE
69                                 .. "%(([^%)]*)%)"  -- readable name
70                                 .. CHAR_CLASSES.SPACE
71                                 .. "(" .. CHAR_CLASSES.VARIABLE .. "+)" -- type
72                                 .. CHAR_CLASSES.SPACE .. "?"
73                         .. ")")
74
75         if not first_part then
76                 return "Invalid line"
77         end
78
79         if name:match("secure%.[.]*") and not allow_secure then
80                 return "Tried to add \"secure.\" setting"
81         end
82
83         if readable_name == "" then
84                 readable_name = nil
85         end
86         local remaining_line = line:sub(first_part:len() + 1)
87
88         if setting_type == "int" then
89                 local default, min, max = remaining_line:match("^"
90                                 -- first int is required, the last 2 are optional
91                                 .. "(" .. CHAR_CLASSES.INTEGER .. "+)" .. CHAR_CLASSES.SPACE .. "?"
92                                 .. "(" .. CHAR_CLASSES.INTEGER .. "*)" .. CHAR_CLASSES.SPACE .. "?"
93                                 .. "(" .. CHAR_CLASSES.INTEGER .. "*)"
94                                 .. "$")
95
96                 if not default or not tonumber(default) then
97                         return "Invalid integer setting"
98                 end
99
100                 min = tonumber(min)
101                 max = tonumber(max)
102                 table.insert(settings, {
103                         name = name,
104                         readable_name = readable_name,
105                         type = "int",
106                         default = default,
107                         min = min,
108                         max = max,
109                         comment = current_comment,
110                 })
111                 return
112         end
113
114         if setting_type == "string" or setting_type == "noise_params"
115                         or setting_type == "key" then
116                 local default = remaining_line:match("^(.*)$")
117
118                 if not default then
119                         return "Invalid string setting"
120                 end
121                 if setting_type == "key" and not read_all then
122                         -- ignore key type if read_all is false
123                         return
124                 end
125
126                 table.insert(settings, {
127                         name = name,
128                         readable_name = readable_name,
129                         type = setting_type,
130                         default = default,
131                         comment = current_comment,
132                 })
133                 return
134         end
135
136         if setting_type == "bool" then
137                 if remaining_line ~= "false" and remaining_line ~= "true" then
138                         return "Invalid boolean setting"
139                 end
140
141                 table.insert(settings, {
142                         name = name,
143                         readable_name = readable_name,
144                         type = "bool",
145                         default = remaining_line,
146                         comment = current_comment,
147                 })
148                 return
149         end
150
151         if setting_type == "float" then
152                 local default, min, max = remaining_line:match("^"
153                                 -- first float is required, the last 2 are optional
154                                 .. "(" .. CHAR_CLASSES.FLOAT .. "+)" .. CHAR_CLASSES.SPACE .. "?"
155                                 .. "(" .. CHAR_CLASSES.FLOAT .. "*)" .. CHAR_CLASSES.SPACE .. "?"
156                                 .. "(" .. CHAR_CLASSES.FLOAT .. "*)"
157                                 .."$")
158
159                 if not default or not tonumber(default) then
160                         return "Invalid float setting"
161                 end
162
163                 min = tonumber(min)
164                 max = tonumber(max)
165                 table.insert(settings, {
166                         name = name,
167                         readable_name = readable_name,
168                         type = "float",
169                         default = default,
170                         min = min,
171                         max = max,
172                         comment = current_comment,
173                 })
174                 return
175         end
176
177         if setting_type == "enum" then
178                 local default, values = remaining_line:match("^(.+)" .. CHAR_CLASSES.SPACE .. "(.+)$")
179
180                 if not default or values == "" then
181                         return "Invalid enum setting"
182                 end
183
184                 table.insert(settings, {
185                         name = name,
186                         readable_name = readable_name,
187                         type = "enum",
188                         default = default,
189                         values = values:split(",", true),
190                         comment = current_comment,
191                 })
192                 return
193         end
194
195         if setting_type == "path" then
196                 local default = remaining_line:match("^(.*)$")
197
198                 if not default then
199                         return "Invalid path setting"
200                 end
201
202                 table.insert(settings, {
203                         name = name,
204                         readable_name = readable_name,
205                         type = "path",
206                         default = default,
207                         comment = current_comment,
208                 })
209                 return
210         end
211
212         if setting_type == "flags" then
213                 local default, possible = remaining_line:match("^"
214                                 .. "(" .. CHAR_CLASSES.FLAGS .. "+)" .. CHAR_CLASSES.SPACE .. ""
215                                 .. "(" .. CHAR_CLASSES.FLAGS .. "+)"
216                                 .. "$")
217
218                 if not default or not possible then
219                         return "Invalid flags setting"
220                 end
221
222                 table.insert(settings, {
223                         name = name,
224                         readable_name = readable_name,
225                         type = "flags",
226                         default = default,
227                         possible = possible,
228                         comment = current_comment,
229                 })
230                 return
231         end
232
233         return "Invalid setting type \"" .. setting_type .. "\""
234 end
235
236 local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure)
237         -- store this helper variable in the table so it's easier to pass to parse_setting_line()
238         result.current_comment = ""
239
240         local line = file:read("*line")
241         while line do
242                 local error_msg = parse_setting_line(result, line, read_all, base_level, allow_secure)
243                 if error_msg then
244                         core.log("error", error_msg .. " in " .. filepath .. " \"" .. line .. "\"")
245                 end
246                 line = file:read("*line")
247         end
248
249         result.current_comment = nil
250 end
251
252 -- read_all: whether to ignore certain setting types for GUI or not
253 -- parse_mods: whether to parse settingtypes.txt in mods and games
254 local function parse_config_file(read_all, parse_mods)
255         local builtin_path = core.get_builtin_path() .. DIR_DELIM .. FILENAME
256         local file = io.open(builtin_path, "r")
257         local settings = {}
258         if not file then
259                 core.log("error", "Can't load " .. FILENAME)
260                 return settings
261         end
262
263         parse_single_file(file, builtin_path, read_all, settings, 0, true)
264
265         file:close()
266
267         if parse_mods then
268                 -- Parse games
269                 local games_category_initialized = false
270                 local index = 1
271                 local game = gamemgr.get_game(index)
272                 while game do
273                         local path = game.path .. DIR_DELIM .. FILENAME
274                         local file = io.open(path, "r")
275                         if file then
276                                 if not games_category_initialized then
277                                         local translation = fgettext_ne("Games"), -- not used, but needed for xgettext
278                                         table.insert(settings, {
279                                                 name = "Games",
280                                                 level = 0,
281                                                 type = "category",
282                                         })
283                                         games_category_initialized = true
284                                 end
285
286                                 table.insert(settings, {
287                                         name = game.name,
288                                         level = 1,
289                                         type = "category",
290                                 })
291
292                                 parse_single_file(file, path, read_all, settings, 2, false)
293
294                                 file:close()
295                         end
296
297                         index = index + 1
298                         game = gamemgr.get_game(index)
299                 end
300
301                 -- Parse mods
302                 local mods_category_initialized = false
303                 local mods = {}
304                 get_mods(core.get_modpath(), mods)
305                 for _, mod in ipairs(mods) do
306                         local path = mod.path .. DIR_DELIM .. FILENAME
307                         local file = io.open(path, "r")
308                         if file then
309                                 if not mods_category_initialized then
310                                         local translation = fgettext_ne("Mods"), -- not used, but needed for xgettext
311                                         table.insert(settings, {
312                                                 name = "Mods",
313                                                 level = 0,
314                                                 type = "category",
315                                         })
316                                         mods_category_initialized = true
317                                 end
318
319                                 table.insert(settings, {
320                                         name = mod.name,
321                                         level = 1,
322                                         type = "category",
323                                 })
324
325                                 parse_single_file(file, path, read_all, settings, 2, false)
326
327                                 file:close()
328                         end
329                 end
330         end
331
332         return settings
333 end
334
335 local settings = parse_config_file(false, true)
336 local selected_setting = 1
337
338 local function get_current_value(setting)
339         local value = core.setting_get(setting.name)
340         if value == nil then
341                 value = setting.default
342         end
343         return value
344 end
345
346 local function create_change_setting_formspec(dialogdata)
347         local setting = settings[selected_setting]
348         local formspec = "size[10,5.2,true]" ..
349                         "button[5,4.5;2,1;btn_done;" .. fgettext("Save") .. "]" ..
350                         "button[3,4.5;2,1;btn_cancel;" .. fgettext("Cancel") .. "]" ..
351                         "tablecolumns[color;text]" ..
352                         "tableoptions[background=#00000000;highlight=#00000000;border=false]" ..
353                         "table[0,0;10,3;info;"
354
355         if setting.readable_name then
356                 formspec = formspec .. "#FFFF00," .. fgettext(setting.readable_name)
357                                 .. " (" .. core.formspec_escape(setting.name) .. "),"
358         else
359                 formspec = formspec .. "#FFFF00," .. core.formspec_escape(setting.name) .. ","
360         end
361
362         formspec = formspec .. ",,"
363
364         local comment_text = ""
365
366         if setting.comment == "" then
367                 comment_text = fgettext_ne("(No description of setting given)")
368         else
369                 comment_text = fgettext_ne(setting.comment)
370         end
371         for _, comment_line in ipairs(comment_text:split("\n", true)) do
372                 formspec = formspec .. "," .. core.formspec_escape(comment_line) .. ","
373         end
374
375         if setting.type == "flags" then
376                 formspec = formspec .. ",,"
377                                 .. "," .. fgettext("Please enter a comma seperated list of flags.") .. ","
378                                 .. "," .. fgettext("Possible values are: ")
379                                 .. core.formspec_escape(setting.possible:gsub(",", ", ")) .. ","
380         elseif setting.type == "noise_params" then
381                 formspec = formspec .. ",,"
382                                 .. "," .. fgettext("Format: <offset>, <scale>, (<spreadX>, <spreadY>, <spreadZ>), <seed>, <octaves>, <persistence>") .. ","
383                                 .. "," .. fgettext("Optionally the lacunarity can be appended with a leading comma.") .. ","
384         end
385
386         formspec = formspec:sub(1, -2) -- remove trailing comma
387
388         formspec = formspec .. ";1]"
389
390         if setting.type == "bool" then
391                 local selected_index
392                 if core.is_yes(get_current_value(setting)) then
393                         selected_index = 2
394                 else
395                         selected_index = 1
396                 end
397                 formspec = formspec .. "dropdown[0.5,3.5;3,1;dd_setting_value;"
398                                 .. fgettext("Disabled") .. "," .. fgettext("Enabled") .. ";"
399                                 .. selected_index .. "]"
400
401         elseif setting.type == "enum" then
402                 local selected_index = 0
403                 formspec = formspec .. "dropdown[0.5,3.5;3,1;dd_setting_value;"
404                 for index, value in ipairs(setting.values) do
405                         -- translating value is not possible, since it's the value
406                         --  that we set the setting to
407                         formspec = formspec ..  core.formspec_escape(value) .. ","
408                         if get_current_value(setting) == value then
409                                 selected_index = index
410                         end
411                 end
412                 if #setting.values > 0 then
413                         formspec = formspec:sub(1, -2) -- remove trailing comma
414                 end
415                 formspec = formspec .. ";" .. selected_index .. "]"
416
417         elseif setting.type == "path" then
418                 local current_value = dialogdata.selected_path
419                 if not current_value then
420                         current_value = get_current_value(setting)
421                 end
422                 formspec = formspec .. "field[0.5,4;7.5,1;te_setting_value;;"
423                                 .. core.formspec_escape(current_value) .. "]"
424                                 .. "button[8,3.75;2,1;btn_browser_path;" .. fgettext("Browse") .. "]"
425
426         else
427                 -- TODO: fancy input for float, int, flags, noise_params
428                 local width = 10
429                 local text = get_current_value(setting)
430                 if dialogdata.error_message then
431                         formspec = formspec .. "tablecolumns[color;text]" ..
432                         "tableoptions[background=#00000000;highlight=#00000000;border=false]" ..
433                         "table[5,4;5,1;error_message;#FF0000,"
434                                         .. core.formspec_escape(dialogdata.error_message) .. ";0]"
435                         width = 5
436                         if dialogdata.entered_text then
437                                 text = dialogdata.entered_text
438                         end
439                 end
440                 formspec = formspec .. "field[0.5,4;" .. width .. ",1;te_setting_value;;"
441                                 .. core.formspec_escape(text) .. "]"
442         end
443         return formspec
444 end
445
446 local function handle_change_setting_buttons(this, fields)
447         if fields["btn_done"] or fields["key_enter"] then
448                 local setting = settings[selected_setting]
449                 if setting.type == "bool" then
450                         local new_value = fields["dd_setting_value"]
451                         -- Note: new_value is the actual (translated) value shown in the dropdown
452                         core.setting_setbool(setting.name, new_value == fgettext("Enabled"))
453
454                 elseif setting.type == "enum" then
455                         local new_value = fields["dd_setting_value"]
456                         core.setting_set(setting.name, new_value)
457
458                 elseif setting.type == "int" then
459                         local new_value = tonumber(fields["te_setting_value"])
460                         if not new_value or math.floor(new_value) ~= new_value then
461                                 this.data.error_message = fgettext_ne("Please enter a valid integer.")
462                                 this.data.entered_text = fields["te_setting_value"]
463                                 core.update_formspec(this:get_formspec())
464                                 return true
465                         end
466                         if setting.min and new_value < setting.min then
467                                 this.data.error_message = fgettext_ne("The value must be greater than $1.", setting.min)
468                                 this.data.entered_text = fields["te_setting_value"]
469                                 core.update_formspec(this:get_formspec())
470                                 return true
471                         end
472                         if setting.max and new_value > setting.max then
473                                 this.data.error_message = fgettext_ne("The value must be lower than $1.", setting.max)
474                                 this.data.entered_text = fields["te_setting_value"]
475                                 core.update_formspec(this:get_formspec())
476                                 return true
477                         end
478                         core.setting_set(setting.name, new_value)
479
480                 elseif setting.type == "float" then
481                         local new_value = tonumber(fields["te_setting_value"])
482                         if not new_value then
483                                 this.data.error_message = fgettext_ne("Please enter a valid number.")
484                                 this.data.entered_text = fields["te_setting_value"]
485                                 core.update_formspec(this:get_formspec())
486                                 return true
487                         end
488                         core.setting_set(setting.name, new_value)
489
490                 elseif setting.type == "flags" then
491                         local new_value = fields["te_setting_value"]
492                         for _,value in ipairs(new_value:split(",", true)) do
493                                 value = value:trim()
494                                 if not value:match(CHAR_CLASSES.FLAGS .. "+")
495                                                 or not setting.possible:match("[,]?" .. value .. "[,]?") then
496                                         this.data.error_message = fgettext_ne("\"$1\" is not a valid flag.", value)
497                                         this.data.entered_text = fields["te_setting_value"]
498                                         core.update_formspec(this:get_formspec())
499                                         return true
500                                 end
501                         end
502                         core.setting_set(setting.name, new_value)
503
504                 else
505                         local new_value = fields["te_setting_value"]
506                         core.setting_set(setting.name, new_value)
507                 end
508                 core.setting_save()
509                 this:delete()
510                 return true
511         end
512
513         if fields["btn_cancel"] then
514                 this:delete()
515                 return true
516         end
517
518         if fields["btn_browser_path"] then
519                 core.show_file_open_dialog("dlg_browse_path", fgettext_ne("Select path"))
520         end
521
522         if fields["dlg_browse_path_accepted"] then
523                 this.data.selected_path = fields["dlg_browse_path_accepted"]
524                 core.update_formspec(this:get_formspec())
525         end
526
527         return false
528 end
529
530 local function create_settings_formspec(tabview, name, tabdata)
531         local formspec = "tablecolumns[color;tree;text;text]" ..
532                                         "tableoptions[background=#00000000;border=false]" ..
533                                         "table[0,0;12,4.5;list_settings;"
534
535         local current_level = 0
536         for _, entry in ipairs(settings) do
537                 local name
538                 if not core.setting_getbool("main_menu_technical_settings") and entry.readable_name then
539                         name = fgettext_ne(entry.readable_name)
540                 else
541                         name = entry.name
542                 end
543
544                 if entry.type == "category" then
545                         current_level = entry.level
546                         formspec = formspec .. "#FFFF00," .. current_level .. "," .. fgettext(name) .. ",,"
547
548                 elseif entry.type == "bool" then
549                         local value = get_current_value(entry)
550                         if core.is_yes(value) then
551                                 value = fgettext("Enabled")
552                         else
553                                 value = fgettext("Disabled")
554                         end
555                         formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
556                                         .. value .. ","
557
558                 elseif entry.type == "key" then
559                         -- ignore key settings, since we have a special dialog for them
560
561                 else
562                         formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
563                                         .. core.formspec_escape(get_current_value(entry)) .. ","
564                 end
565         end
566
567         if #settings > 0 then
568                 formspec = formspec:sub(1, -2) -- remove trailing comma
569         end
570         formspec = formspec .. ";" .. selected_setting .. "]" ..
571                         "button[4,4.5;3,1;btn_change_keys;".. fgettext("Change keys") .. "]" ..
572                         "button[10,4.5;2,1;btn_edit;" .. fgettext("Edit") .. "]" ..
573                         "button[7,4.5;3,1;btn_restore;" .. fgettext("Restore Default") .. "]" ..
574                         "checkbox[0,4.5;cb_tech_settings;" .. fgettext("Show technical names") .. ";"
575                                         .. dump(core.setting_getbool("main_menu_technical_settings")) .. "]"
576
577         return formspec
578 end
579
580 local function handle_settings_buttons(this, fields, tabname, tabdata)
581         local list_enter = false
582         if fields["list_settings"] then
583                 selected_setting = core.get_table_index("list_settings")
584                 if  core.explode_table_event(fields["list_settings"]).type == "DCL" then
585                         -- Directly toggle booleans
586                         local setting = settings[selected_setting]
587                         if setting.type == "bool" then
588                                 local current_value = get_current_value(setting)
589                                 core.setting_setbool(setting.name, not core.is_yes(current_value))
590                                 core.setting_save()
591                                 return true
592                         else
593                                 list_enter = true
594                         end
595                 else
596                         return true
597                 end
598         end
599
600         if fields["btn_edit"] or list_enter then
601                 local setting = settings[selected_setting]
602                 if setting.type ~= "category" then
603                         local edit_dialog = dialog_create("change_setting", create_change_setting_formspec,
604                                         handle_change_setting_buttons)
605                         edit_dialog:set_parent(this)
606                         this:hide()
607                         edit_dialog:show()
608                 end
609                 return true
610         end
611
612         if fields["btn_restore"] then
613                 local setting = settings[selected_setting]
614                 if setting.type ~= "category" then
615                         core.setting_set(setting.name, setting.default)
616                         core.setting_save()
617                         core.update_formspec(this:get_formspec())
618                 end
619                 return true
620         end
621
622         if fields["btn_change_keys"] then
623                 core.show_keys_menu()
624                 return true
625         end
626
627         if fields["cb_tech_settings"] then
628                 core.setting_set("main_menu_technical_settings", fields["cb_tech_settings"])
629                 core.setting_save()
630                 core.update_formspec(this:get_formspec())
631                 return true
632         end
633
634         return false
635 end
636
637 tab_settings = {
638         name = "settings",
639         caption = fgettext("Settings"),
640         cbf_formspec = create_settings_formspec,
641         cbf_button_handler = handle_settings_buttons,
642 }
643
644 local function create_minetest_conf_example()
645         local result = "#    This file contains a list of all available settings and their default value for minetest.conf\n" ..
646                         "\n" ..
647                         "#    By default, all the settings are commented and not functional.\n" ..
648                         "#    Uncomment settings by removing the preceding #.\n" ..
649                         "\n" ..
650                         "#    minetest.conf is read by default from:\n" ..
651                         "#    ../minetest.conf\n" ..
652                         "#    ../../minetest.conf\n" ..
653                         "#    Any other path can be chosen by passing the path as a parameter\n" ..
654                         "#    to the program, eg. \"minetest.exe --config ../minetest.conf.example\".\n" ..
655                         "\n" ..
656                         "#    Further documentation:\n" ..
657                         "#    http://wiki.minetest.net/\n" ..
658                         "\n"
659
660         local settings = parse_config_file(true, false)
661         for _, entry in ipairs(settings) do
662                 if entry.type == "category" then
663                         if entry.level == 0 then
664                                 result = result .. "#\n# " .. entry.name .. "\n#\n\n"
665                         else
666                                 for i = 1, entry.level do
667                                         result = result .. "#"
668                                 end
669                                 result = result .. "# " .. entry.name .. "\n\n"
670                         end
671                 else
672                         if entry.comment ~= "" then
673                                 for _, comment_line in ipairs(entry.comment:split("\n", true)) do
674                                         result = result .."#    " .. comment_line .. "\n"
675                                 end
676                         end
677                         result = result .. "#    type: " .. entry.type
678                         if entry.min then
679                                 result = result .. " min: " .. entry.min
680                         end
681                         if entry.max then
682                                 result = result .. " max: " .. entry.max
683                         end
684                         if entry.values then
685                                 result = result .. " values: " .. table.concat(entry.values, ", ")
686                         end
687                         if entry.possible then
688                                 result = result .. " possible values: " .. entry.possible:gsub(",", ", ")
689                         end
690                         result = result .. "\n"
691                         result = result .. "# " .. entry.name .. " = ".. entry.default .. "\n\n"
692                 end
693         end
694         return result
695 end
696
697 local function create_translation_file()
698         local result = "// This file is automatically generated\n" ..
699                         "// It conatins a bunch of fake gettext calls, to tell xgettext about the strings in config files\n" ..
700                         "// To update it, refer to the bottom of builtin/mainmenu/tab_settings.lua\n\n" ..
701                         "fake_function() {\n"
702
703         local settings = parse_config_file(true, false)
704         for _, entry in ipairs(settings) do
705                 if entry.type == "category" then
706                         local name_escaped = entry.name:gsub("\"", "\\\"")
707                         result = result .. "\tgettext(\"" .. name_escaped .. "\");\n"
708                 else
709                         if entry.readable_name then
710                                 local readable_name_escaped = entry.readable_name:gsub("\"", "\\\"")
711                                 result = result .. "\tgettext(\"" .. readable_name_escaped .. "\");\n"
712                         end
713                         if entry.comment ~= "" then
714                                 local comment_escaped = entry.comment:gsub("\n", "\\n")
715                                 comment_escaped = comment_escaped:gsub("\"", "\\\"")
716                                 result = result .. "\tgettext(\"" .. comment_escaped .. "\");\n"
717                         end
718                 end
719         end
720         result = result .. "}\n"
721         return result
722 end
723
724 if false then
725         local file = io.open("minetest.conf.example", "w")
726         if file then
727                 file:write(create_minetest_conf_example())
728                 file:close()
729         end
730 end
731
732 if false then
733         local file = io.open("src/settings_translation_file.cpp", "w")
734         if file then
735                 file:write(create_translation_file())
736                 file:close()
737         end
738 end