diff --git a/src/game/game_text.hpp b/src/game/game_text.hpp
index 60a10e2dcc..f85c8aecd3 100644
--- a/src/game/game_text.hpp
+++ b/src/game/game_text.hpp
@@ -22,9 +22,9 @@ struct StringParam {
 
 	ParamType type;
 	uint8_t consumes;
-	const char *cmd;
+	std::string_view cmd;
 
-	StringParam(ParamType type, uint8_t consumes, const char *cmd) : type(type), consumes(consumes), cmd(cmd) {}
+	StringParam(ParamType type, uint8_t consumes, std::string_view cmd) : type(type), consumes(consumes), cmd(cmd) {}
 };
 using StringParams = std::vector<StringParam>;
 using StringParamsList = std::vector<StringParams>;
diff --git a/src/language.h b/src/language.h
index c67df2bda1..603ca8cfdd 100644
--- a/src/language.h
+++ b/src/language.h
@@ -65,10 +65,10 @@ struct LanguagePackHeader {
 	 * @param gender_str The string representation of the gender.
 	 * @return The index of the gender, or MAX_NUM_GENDERS when the gender is unknown.
 	 */
-	uint8_t GetGenderIndex(const char *gender_str) const
+	uint8_t GetGenderIndex(std::string_view gender_str) const
 	{
 		for (uint8_t i = 0; i < MAX_NUM_GENDERS; i++) {
-			if (strcmp(gender_str, this->genders[i]) == 0) return i;
+			if (gender_str.compare(this->genders[i]) == 0) return i;
 		}
 		return MAX_NUM_GENDERS;
 	}
@@ -78,10 +78,10 @@ struct LanguagePackHeader {
 	 * @param case_str The string representation of the case.
 	 * @return The index of the case, or MAX_NUM_CASES when the case is unknown.
 	 */
-	uint8_t GetCaseIndex(const char *case_str) const
+	uint8_t GetCaseIndex(std::string_view case_str) const
 	{
 		for (uint8_t i = 0; i < MAX_NUM_CASES; i++) {
-			if (strcmp(case_str, this->cases[i]) == 0) return i;
+			if (case_str.compare(this->cases[i]) == 0) return i;
 		}
 		return MAX_NUM_CASES;
 	}
diff --git a/src/newgrf/newgrf_act0_globalvar.cpp b/src/newgrf/newgrf_act0_globalvar.cpp
index edce43e7a1..803995d937 100644
--- a/src/newgrf/newgrf_act0_globalvar.cpp
+++ b/src/newgrf/newgrf_act0_globalvar.cpp
@@ -302,14 +302,14 @@ static ChangeInfoResult GlobalVarChangeInfo(uint first, uint last, int prop, Byt
 					LanguageMap::Mapping map;
 					map.newgrf_id = newgrf_id;
 					if (prop == 0x13) {
-						map.openttd_id = lang->GetGenderIndex(name.data());
+						map.openttd_id = lang->GetGenderIndex(name);
 						if (map.openttd_id >= MAX_NUM_GENDERS) {
 							GrfMsg(1, "GlobalVarChangeInfo: Gender name {} is not known, ignoring", StrMakeValid(name));
 						} else {
 							_cur.grffile->language_map[curidx].gender_map.push_back(map);
 						}
 					} else {
-						map.openttd_id = lang->GetCaseIndex(name.data());
+						map.openttd_id = lang->GetCaseIndex(name);
 						if (map.openttd_id >= MAX_NUM_CASES) {
 							GrfMsg(1, "GlobalVarChangeInfo: Case name {} is not known, ignoring", StrMakeValid(name));
 						} else {
diff --git a/src/script/api/script_text.cpp b/src/script/api/script_text.cpp
index 630c432b28..7f963187bc 100644
--- a/src/script/api/script_text.cpp
+++ b/src/script/api/script_text.cpp
@@ -193,9 +193,9 @@ void ScriptText::_FillParamList(ParamList &params, ScriptTextList &seen_texts)
 	}
 }
 
-void ScriptText::ParamCheck::Encode(std::back_insert_iterator<std::string> &output, const char *cmd)
+void ScriptText::ParamCheck::Encode(std::back_insert_iterator<std::string> &output, std::string_view cmd)
 {
-	if (this->cmd == nullptr) this->cmd = cmd;
+	if (this->cmd.empty()) this->cmd = cmd;
 	if (this->used) return;
 
 	struct visitor {
@@ -286,7 +286,7 @@ void ScriptText::_GetEncodedText(std::back_insert_iterator<std::string> &output,
 				default:
 					for (int i = 0; i < cur_param.consumes; i++) {
 						ParamCheck &p = *get_next_arg();
-						p.Encode(output, i == 0 ? cur_param.cmd : nullptr);
+						p.Encode(output, i == 0 ? cur_param.cmd : "");
 						if (i == 0 && p.cmd != cur_param.cmd) throw 1;
 						if (!std::holds_alternative<SQInteger>(*p.param)) ScriptLog::Error(fmt::format("{}({}): {{{}}} expects an integer", name, param_count + i + 1, cur_param.cmd));
 					}
diff --git a/src/script/api/script_text.hpp b/src/script/api/script_text.hpp
index 02c757a427..c31da8a400 100644
--- a/src/script/api/script_text.hpp
+++ b/src/script/api/script_text.hpp
@@ -137,11 +137,11 @@ private:
 		int idx;
 		Param *param;
 		bool used = false;
-		const char *cmd = nullptr;
+		std::string_view cmd;
 
 		ParamCheck(StringIndexInTab owner, int idx, Param *param) : owner(owner), idx(idx), param(param) {}
 
-		void Encode(std::back_insert_iterator<std::string> &output, const char *cmd);
+		void Encode(std::back_insert_iterator<std::string> &output, std::string_view cmd);
 	};
 
 	using ParamList = std::vector<ParamCheck>;
diff --git a/src/strgen/strgen.cpp b/src/strgen/strgen.cpp
index 1cdb9c8225..7b4287eec5 100644
--- a/src/strgen/strgen.cpp
+++ b/src/strgen/strgen.cpp
@@ -146,26 +146,26 @@ void FileStringReader::HandlePragma(char *str)
 		_lang.newgrflangid = static_cast<uint8_t>(langid);
 	} else if (!memcmp(str, "gender ", 7)) {
 		if (this->master) FatalError("Genders are not allowed in the base translation.");
-		char *buf = str + 7;
+		const char *buf = str + 7;
 
 		for (;;) {
-			const char *s = ParseWord(&buf);
+			auto s = ParseWord(&buf);
 
-			if (s == nullptr) break;
+			if (!s.has_value()) break;
 			if (_lang.num_genders >= MAX_NUM_GENDERS) FatalError("Too many genders, max {}", MAX_NUM_GENDERS);
-			strecpy(_lang.genders[_lang.num_genders], s);
+			s->copy(_lang.genders[_lang.num_genders], CASE_GENDER_LEN - 1);
 			_lang.num_genders++;
 		}
 	} else if (!memcmp(str, "case ", 5)) {
 		if (this->master) FatalError("Cases are not allowed in the base translation.");
-		char *buf = str + 5;
+		const char *buf = str + 5;
 
 		for (;;) {
-			const char *s = ParseWord(&buf);
+			auto s = ParseWord(&buf);
 
-			if (s == nullptr) break;
+			if (!s.has_value()) break;
 			if (_lang.num_cases >= MAX_NUM_CASES) FatalError("Too many cases, max {}", MAX_NUM_CASES);
-			strecpy(_lang.cases[_lang.num_cases], s);
+			s->copy(_lang.cases[_lang.num_cases], CASE_GENDER_LEN - 1);
 			_lang.num_cases++;
 		}
 	} else {
@@ -344,7 +344,7 @@ int CDECL main(int argc, char *argv[])
 					} else {
 						flags = '0'; // Command needs no parameters
 					}
-					fmt::print("{}\t{:c}\t\"{}\"\t\"{}\"\n", cs.consumes, flags, cs.cmd, strstr(cs.cmd, "STRING") ? "STRING" : cs.cmd);
+					fmt::print("{}\t{:c}\t\"{}\"\t\"{}\"\n", cs.consumes, flags, cs.cmd, cs.cmd.find("STRING") != std::string::npos ? "STRING" : cs.cmd);
 				}
 				return 0;
 
diff --git a/src/strgen/strgen.h b/src/strgen/strgen.h
index bb6511661a..7253138063 100644
--- a/src/strgen/strgen.h
+++ b/src/strgen/strgen.h
@@ -152,7 +152,7 @@ void StrgenErrorI(const std::string &msg);
 #define StrgenWarning(format_string, ...) StrgenWarningI(fmt::format(FMT_STRING(format_string) __VA_OPT__(,) __VA_ARGS__))
 #define StrgenError(format_string, ...) StrgenErrorI(fmt::format(FMT_STRING(format_string) __VA_OPT__(,) __VA_ARGS__))
 #define StrgenFatal(format_string, ...) StrgenFatalI(fmt::format(FMT_STRING(format_string) __VA_OPT__(,) __VA_ARGS__))
-char *ParseWord(char **buf);
+std::optional<std::string_view> ParseWord(const char **buf);
 
 extern const char *_file;
 extern size_t _cur_line;
diff --git a/src/strgen/strgen_base.cpp b/src/strgen/strgen_base.cpp
index e124504917..b4964cb4a7 100644
--- a/src/strgen/strgen_base.cpp
+++ b/src/strgen/strgen_base.cpp
@@ -118,10 +118,10 @@ LangString *StringData::Find(const std::string &s)
  * @param s    The string hash.
  * @return The new hash.
  */
-static uint32_t VersionHashStr(uint32_t hash, const char *s)
+static uint32_t VersionHashStr(uint32_t hash, std::string_view s)
 {
-	for (; *s != '\0'; s++) {
-		hash = std::rotl(hash, 3) ^ *s;
+	for (auto c : s) {
+		hash = std::rotl(hash, 3) ^ c;
 		hash = (hash & 1 ? hash >> 1 ^ 0xDEADBEEF : hash >> 1);
 	}
 	return hash;
@@ -139,12 +139,11 @@ uint32_t StringData::Version() const
 		const LangString *ls = this->strings[i].get();
 
 		if (ls != nullptr) {
-			const char *s = ls->name.c_str();
 			hash ^= i * 0x717239;
 			hash = (hash & 1 ? hash >> 1 ^ 0xDEADBEEF : hash >> 1);
-			hash = VersionHashStr(hash, s + 1);
+			hash = VersionHashStr(hash, ls->name);
 
-			s = ls->english.c_str();
+			const char *s = ls->english.c_str();
 			ParsedCommandString cs;
 			while ((cs = ParseCommandString(&s)).cmd != nullptr) {
 				if (cs.cmd->flags.Test(CmdFlag::DontCount)) continue;
@@ -230,7 +229,7 @@ static size_t Utf8Validate(const char *s)
 	return 0;
 }
 
-void EmitSingleChar(Buffer *buffer, char *buf, char32_t value)
+void EmitSingleChar(Buffer *buffer, const char *buf, char32_t value)
 {
 	if (*buf != '\0') StrgenWarning("Ignoring trailing letters in command");
 	buffer->AppendUtf8(value);
@@ -238,7 +237,7 @@ void EmitSingleChar(Buffer *buffer, char *buf, char32_t value)
 
 /* The plural specifier looks like
  * {NUM} {PLURAL <ARG#> passenger passengers} then it picks either passenger/passengers depending on the count in NUM */
-static std::pair<std::optional<size_t>, std::optional<size_t>> ParseRelNum(char **buf)
+static std::pair<std::optional<size_t>, std::optional<size_t>> ParseRelNum(const char **buf)
 {
 	const char *s = *buf;
 	char *end;
@@ -258,65 +257,58 @@ static std::pair<std::optional<size_t>, std::optional<size_t>> ParseRelNum(char
 }
 
 /* Parse out the next word, or nullptr */
-char *ParseWord(char **buf)
+std::optional<std::string_view> ParseWord(const char **buf)
 {
-	char *s = *buf, *r;
+	const char *s = *buf;
 
 	while (*s == ' ' || *s == '\t') s++;
-	if (*s == '\0') return nullptr;
+	if (*s == '\0') return {};
 
 	if (*s == '"') {
-		r = ++s;
+		const char *begin = ++s;
 		/* parse until next " or NUL */
 		for (;;) {
-			if (*s == '\0') break;
+			if (*s == '\0') StrgenFatal("Unterminated quotes");
 			if (*s == '"') {
-				*s++ = '\0';
-				break;
+				*buf = s + 1;
+				return std::string_view(begin, s - begin);
 			}
 			s++;
 		}
 	} else {
 		/* proceed until whitespace or NUL */
-		r = s;
+		const char *begin = s;
 		for (;;) {
-			if (*s == '\0') break;
-			if (*s == ' ' || *s == '\t') {
-				*s++ = '\0';
-				break;
+			if (*s == '\0' || *s == ' ' || *s == '\t') {
+				*buf = s;
+				return std::string_view(begin, s - begin);
 			}
 			s++;
 		}
 	}
-	*buf = s;
-	return r;
 }
 
 /* This is encoded like
  *  CommandByte <ARG#> <NUM> {Length of each string} {each string} */
-static void EmitWordList(Buffer *buffer, const std::vector<const char *> &words, size_t nw)
+static void EmitWordList(Buffer *buffer, const std::vector<std::string> &words)
 {
 	/* Maximum word length in bytes, excluding trailing NULL. */
 	constexpr size_t MAX_WORD_LENGTH = UINT8_MAX - 2;
 
-	buffer->AppendByte(static_cast<uint8_t>(nw));
-	for (size_t i = 0; i < nw; i++) {
-		size_t len = strlen(words[i]) + 1;
-		if (len >= UINT8_MAX) StrgenFatal("WordList {}/{} string '{}' too long, max bytes {}", i + 1, nw, words[i], MAX_WORD_LENGTH);
+	buffer->AppendByte(static_cast<uint8_t>(words.size()));
+	for (size_t i = 0; i < words.size(); i++) {
+		size_t len = words[i].size() + 1;
+		if (len >= UINT8_MAX) StrgenFatal("WordList {}/{} string '{}' too long, max bytes {}", i + 1, words.size(), words[i], MAX_WORD_LENGTH);
 		buffer->AppendByte(static_cast<uint8_t>(len));
 	}
-	for (size_t i = 0; i < nw; i++) {
+	for (size_t i = 0; i < words.size(); i++) {
 		buffer->append(words[i]);
 		buffer->AppendByte(0);
 	}
 }
 
-void EmitPlural(Buffer *buffer, char *buf, char32_t)
+void EmitPlural(Buffer *buffer, const char *buf, char32_t)
 {
-	size_t expected = _plural_forms[_lang.plural_form].plural_count;
-	std::vector<const char *> words(std::max(expected, MAX_PLURALS), nullptr);
-	size_t nw = 0;
-
 	/* Parse out the number, if one exists. Otherwise default to prev arg. */
 	auto [argidx, offset] = ParseRelNum(&buf);
 	if (!argidx.has_value()) {
@@ -334,26 +326,29 @@ void EmitPlural(Buffer *buffer, char *buf, char32_t)
 	}
 
 	/* Parse each string */
-	for (nw = 0; nw < MAX_PLURALS; nw++) {
-		words[nw] = ParseWord(&buf);
-		if (words[nw] == nullptr) break;
+	std::vector<std::string> words;
+	for (;;) {
+		auto word = ParseWord(&buf);
+		if (!word.has_value()) break;
+		words.emplace_back(*word);
 	}
 
-	if (nw == 0) {
+	if (words.empty()) {
 		StrgenFatal("{}: No plural words", _cur_ident);
 	}
 
-	if (expected != nw) {
+	size_t expected = _plural_forms[_lang.plural_form].plural_count;
+	if (expected != words.size()) {
 		if (_translated) {
 			StrgenFatal("{}: Invalid number of plural forms. Expecting {}, found {}.", _cur_ident,
-				expected, nw);
+				expected, words.size());
 		} else {
 			if (_show_warnings) StrgenWarning("'{}' is untranslated. Tweaking english string to allow compilation for plural forms", _cur_ident);
-			if (nw > expected) {
-				nw = expected;
+			if (words.size() > expected) {
+				words.resize(expected);
 			} else {
-				for (; nw < expected; nw++) {
-					words[nw] = words[nw - 1];
+				while (words.size() < expected) {
+					words.push_back(words.back());
 				}
 			}
 		}
@@ -362,24 +357,22 @@ void EmitPlural(Buffer *buffer, char *buf, char32_t)
 	buffer->AppendUtf8(SCC_PLURAL_LIST);
 	buffer->AppendByte(_lang.plural_form);
 	buffer->AppendByte(static_cast<uint8_t>(TranslateArgumentIdx(*argidx, *offset)));
-	EmitWordList(buffer, words, nw);
+	EmitWordList(buffer, words);
 }
 
-void EmitGender(Buffer *buffer, char *buf, char32_t)
+void EmitGender(Buffer *buffer, const char *buf, char32_t)
 {
 	if (buf[0] == '=') {
 		buf++;
 
 		/* This is a {G=DER} command */
-		uint nw = _lang.GetGenderIndex(buf);
+		auto nw = _lang.GetGenderIndex(buf);
 		if (nw >= MAX_NUM_GENDERS) StrgenFatal("G argument '{}' invalid", buf);
 
 		/* now nw contains the gender index */
 		buffer->AppendUtf8(SCC_GENDER_INDEX);
 		buffer->AppendByte(nw);
 	} else {
-		std::vector<const char *> words(MAX_NUM_GENDERS, nullptr);
-
 		/* This is a {G 0 foo bar two} command.
 		 * If no relative number exists, default to +0 */
 		auto [argidx, offset] = ParseRelNum(&buf);
@@ -391,38 +384,33 @@ void EmitGender(Buffer *buffer, char *buf, char32_t)
 			StrgenFatal("Command '{}' can't have a gender", cmd == nullptr ? "<empty>" : cmd->cmd);
 		}
 
-		size_t nw;
-		for (nw = 0; nw < MAX_NUM_GENDERS; nw++) {
-			words[nw] = ParseWord(&buf);
-			if (words[nw] == nullptr) break;
+		std::vector<std::string> words;
+		for (;;) {
+			auto word = ParseWord(&buf);
+			if (!word.has_value()) break;
+			words.emplace_back(*word);
 		}
-		if (nw != _lang.num_genders) StrgenFatal("Bad # of arguments for gender command");
+		if (words.size() != _lang.num_genders) StrgenFatal("Bad # of arguments for gender command");
 
 		assert(IsInsideBS(cmd->value, SCC_CONTROL_START, UINT8_MAX));
 		buffer->AppendUtf8(SCC_GENDER_LIST);
 		buffer->AppendByte(static_cast<uint8_t>(TranslateArgumentIdx(*argidx, *offset)));
-		EmitWordList(buffer, words, nw);
+		EmitWordList(buffer, words);
 	}
 }
 
-static const CmdStruct *FindCmd(const char *s, size_t len)
+static const CmdStruct *FindCmd(std::string_view s)
 {
 	for (const auto &cs : _cmd_structs) {
-		if (strncmp(cs.cmd, s, len) == 0 && cs.cmd[len] == '\0') return &cs;
+		if (cs.cmd == s) return &cs;
 	}
 	return nullptr;
 }
 
-static uint ResolveCaseName(const char *str, size_t len)
+static uint ResolveCaseName(std::string_view str)
 {
-	/* First get a clean copy of only the case name, then resolve it. */
-	char case_str[CASE_GENDER_LEN];
-	len = std::min(lengthof(case_str) - 1, len);
-	memcpy(case_str, str, len);
-	case_str[len] = '\0';
-
-	uint case_idx = _lang.GetCaseIndex(case_str);
-	if (case_idx >= MAX_NUM_CASES) StrgenFatal("Invalid case-name '{}'", case_str);
+	uint case_idx = _lang.GetCaseIndex(str);
+	if (case_idx >= MAX_NUM_CASES) StrgenFatal("Invalid case-name '{}'", str);
 	return case_idx + 1;
 }
 
@@ -453,9 +441,9 @@ static ParsedCommandString ParseCommandString(const char **str)
 		c = *s++;
 	} while (c != '}' && c != ' ' && c != '=' && c != '.' && c != 0);
 
-	result.cmd = FindCmd(start, s - start - 1);
+	std::string_view command(start, s - start - 1);
+	result.cmd = FindCmd(command);
 	if (result.cmd == nullptr) {
-		std::string command(start, s - start - 1);
 		StrgenError("Undefined command '{}'", command);
 		return {};
 	}
@@ -470,7 +458,7 @@ static ParsedCommandString ParseCommandString(const char **str)
 		do {
 			c = *s++;
 		} while (c != '}' && c != ' ' && c != '\0');
-		result.casei = ResolveCaseName(casep, s - casep - 1);
+		result.casei = ResolveCaseName(std::string_view(casep, s - casep - 1));
 	}
 
 	if (c == '\0') {
@@ -542,15 +530,15 @@ const CmdStruct *TranslateCmdForCompare(const CmdStruct *a)
 {
 	if (a == nullptr) return nullptr;
 
-	if (strcmp(a->cmd, "STRING1") == 0 ||
-			strcmp(a->cmd, "STRING2") == 0 ||
-			strcmp(a->cmd, "STRING3") == 0 ||
-			strcmp(a->cmd, "STRING4") == 0 ||
-			strcmp(a->cmd, "STRING5") == 0 ||
-			strcmp(a->cmd, "STRING6") == 0 ||
-			strcmp(a->cmd, "STRING7") == 0 ||
-			strcmp(a->cmd, "RAW_STRING") == 0) {
-		return FindCmd("STRING", 6);
+	if (a->cmd == "STRING1" ||
+			a->cmd == "STRING2" ||
+			a->cmd == "STRING3" ||
+			a->cmd == "STRING4" ||
+			a->cmd == "STRING5" ||
+			a->cmd == "STRING6" ||
+			a->cmd == "STRING7" ||
+			a->cmd == "RAW_STRING") {
+		return FindCmd("STRING");
 	}
 
 	return a;
@@ -689,7 +677,7 @@ void StringReader::HandleString(char *str)
 		if (!CheckCommandsMatch(s, ent->english.c_str(), str)) return;
 
 		if (casep != nullptr) {
-			ent->translated_cases.emplace_back(ResolveCaseName(casep, strlen(casep)), s);
+			ent->translated_cases.emplace_back(ResolveCaseName(casep), s);
 		} else {
 			ent->translated = s;
 			/* If the string was translated, use the line from the
@@ -827,7 +815,7 @@ static void PutCommandString(Buffer *buffer, const char *str)
 			}
 		}
 
-		cmd->proc(buffer, cs.param.data(), cmd->value);
+		cmd->proc(buffer, cs.param.c_str(), cmd->value);
 	}
 }
 
diff --git a/src/table/strgen_tables.h b/src/table/strgen_tables.h
index c17074d0ef..0c8355f05c 100644
--- a/src/table/strgen_tables.h
+++ b/src/table/strgen_tables.h
@@ -17,10 +17,10 @@ enum class CmdFlag : uint8_t {
 using CmdFlags = EnumBitSet<CmdFlag, uint8_t>;
 
 struct Buffer;
-typedef void (*ParseCmdProc)(Buffer *buffer, char *buf, char32_t value);
+typedef void (*ParseCmdProc)(Buffer *buffer, const char *buf, char32_t value);
 
 struct CmdStruct {
-	const char *cmd;
+	std::string_view cmd;
 	ParseCmdProc proc;
 	char32_t value;
 	uint8_t consumes;
@@ -28,9 +28,9 @@ struct CmdStruct {
 	CmdFlags flags;
 };
 
-extern void EmitSingleChar(Buffer *buffer, char *buf, char32_t value);
-extern void EmitPlural(Buffer *buffer, char *buf, char32_t value);
-extern void EmitGender(Buffer *buffer, char *buf, char32_t value);
+extern void EmitSingleChar(Buffer *buffer, const char *buf, char32_t value);
+extern void EmitPlural(Buffer *buffer, const char *buf, char32_t value);
+extern void EmitGender(Buffer *buffer, const char *buf, char32_t value);
 
 static const CmdStruct _cmd_structs[] = {
 	/* Font size */