diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/mainmenu/settings/dlg_settings.lua
index c3cd04f6a..01ff2dcb0 100644
--- a/builtin/mainmenu/settings/dlg_settings.lua
+++ b/builtin/mainmenu/settings/dlg_settings.lua
@@ -116,9 +116,7 @@ local function load_settingtypes()
 					content = {},
 				}
 
-				if page.title:sub(1, 5) ~= "Hide:" then
-					page = add_page(page)
-				end
+				page = add_page(page)
 			elseif entry.level == 2 then
 				ensure_page_started()
 				page.content[#page.content + 1] = {
diff --git a/builtin/mainmenu/settings/generate_from_settingtypes.lua b/builtin/mainmenu/settings/generate_from_settingtypes.lua
index 79bc94c51..e18b2c2d6 100644
--- a/builtin/mainmenu/settings/generate_from_settingtypes.lua
+++ b/builtin/mainmenu/settings/generate_from_settingtypes.lua
@@ -1,5 +1,3 @@
-local settings = ...
-
 local concat = table.concat
 local insert = table.insert
 local sprintf = string.format
@@ -36,7 +34,7 @@ local group_format_template = [[
 
 ]]
 
-local function create_minetest_conf_example()
+local function create_minetest_conf_example(settings)
 	local result = { minetest_example_header }
 	for _, entry in ipairs(settings) do
 		if entry.type == "category" then
@@ -108,14 +106,11 @@ local translation_file_header = [[
 
 fake_function() {]]
 
-local function create_translation_file()
+local function create_translation_file(settings)
 	local result = { translation_file_header }
 	for _, entry in ipairs(settings) do
 		if entry.type == "category" then
 			insert(result, sprintf("\tgettext(%q);", entry.name))
-		elseif entry.type == "key" then --luacheck: ignore
-			-- Neither names nor descriptions of keys are used since we have a
-			-- dedicated menu for them.
 		else
 			if entry.readable_name then
 				insert(result, sprintf("\tgettext(%q);", entry.readable_name))
@@ -132,12 +127,13 @@ local function create_translation_file()
 end
 
 local file = assert(io.open("minetest.conf.example", "w"))
-file:write(create_minetest_conf_example())
+file:write(create_minetest_conf_example(settingtypes.parse_config_file(true, false)))
 file:close()
 
 file = assert(io.open("src/settings_translation_file.cpp", "w"))
 -- If 'minetest.conf.example' appears in the 'bin' folder, the line below may have to be
 -- used instead. The file will also appear in the 'bin' folder.
 --file = assert(io.open("settings_translation_file.cpp", "w"))
-file:write(create_translation_file())
+-- We don't want hidden settings to be translated, so we set read_all to false.
+file:write(create_translation_file(settingtypes.parse_config_file(false, false)))
 file:close()
diff --git a/builtin/mainmenu/settings/init.lua b/builtin/mainmenu/settings/init.lua
index c60f1ef18..4541468c1 100644
--- a/builtin/mainmenu/settings/init.lua
+++ b/builtin/mainmenu/settings/init.lua
@@ -25,4 +25,4 @@ dofile(path .. DIR_DELIM .. "dlg_settings.lua")
 -- For RUN_IN_PLACE the generated files may appear in the 'bin' folder.
 -- See comment and alternative line at the end of 'generate_from_settingtypes.lua'.
 
--- assert(loadfile(path .. DIR_DELIM .. "generate_from_settingtypes.lua"))(settingtypes.parse_config_file(true, false))
+-- dofile(path .. DIR_DELIM .. "generate_from_settingtypes.lua")
diff --git a/builtin/mainmenu/settings/settingtypes.lua b/builtin/mainmenu/settings/settingtypes.lua
index 891e89fcb..1174c9b76 100644
--- a/builtin/mainmenu/settings/settingtypes.lua
+++ b/builtin/mainmenu/settings/settingtypes.lua
@@ -70,14 +70,37 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se
 	-- category
 	local stars, category = line:match("^%[([%*]*)([^%]]+)%]$")
 	if category then
+		local category_level = stars:len() + base_level
+
+		if settings.current_hide_level then
+			if settings.current_hide_level < category_level then
+				-- Skip this category, it's inside a hidden category.
+				return
+			else
+				-- The start of this category marks the end of a hidden category.
+				settings.current_hide_level = nil
+			end
+		end
+
+		if not read_all and category:sub(1, 5) == "Hide:" then
+			-- This category is hidden.
+			settings.current_hide_level = category_level
+			return
+		end
+
 		table.insert(settings, {
 			name = category,
-			level = stars:len() + base_level,
+			level = category_level,
 			type = "category",
 		})
 		return
 	end
 
+	if settings.current_hide_level then
+		-- Ignore this line, we're inside a hidden category.
+		return
+	end
+
 	-- settings
 	local first_part, name, readable_name, setting_type = line:match("^"
 			-- this first capture group matches the whole first part,
@@ -349,6 +372,7 @@ end
 local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure)
 	-- store this helper variable in the table so it's easier to pass to parse_setting_line()
 	result.current_comment = {}
+	result.current_hide_level = nil
 
 	local line = file:read("*line")
 	while line do
@@ -360,6 +384,7 @@ local function parse_single_file(file, filepath, read_all, result, base_level, a
 	end
 
 	result.current_comment = nil
+	result.current_hide_level = nil
 end