Update: Translations from eints
[openttd-github.git] / src / game / game_text.cpp
blob3edd778512a1363db610455f691bd18555759f51
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file game_text.cpp Implementation of handling translated strings. */
10 #include "../stdafx.h"
11 #include "../strgen/strgen.h"
12 #include "../debug.h"
13 #include "../fileio_func.h"
14 #include "../tar_type.h"
15 #include "../script/squirrel_class.hpp"
16 #include "../strings_func.h"
17 #include "game_text.hpp"
18 #include "game.hpp"
19 #include "game_info.hpp"
21 #include "table/strings.h"
22 #include "table/strgen_tables.h"
24 #include "../safeguards.h"
26 void CDECL StrgenWarningI(const std::string &msg)
28 Debug(script, 0, "{}:{}: warning: {}", _file, _cur_line, msg);
29 _warnings++;
32 void CDECL StrgenErrorI(const std::string &msg)
34 Debug(script, 0, "{}:{}: error: {}", _file, _cur_line, msg);
35 _errors++;
38 void CDECL StrgenFatalI(const std::string &msg)
40 Debug(script, 0, "{}:{}: FATAL: {}", _file, _cur_line, msg);
41 throw std::exception();
44 /**
45 * Read all the raw language strings from the given file.
46 * @param file The file to read from.
47 * @return The raw strings, or nullptr upon error.
49 LanguageStrings ReadRawLanguageStrings(const std::string &file)
51 size_t to_read;
52 auto fh = FioFOpenFile(file, "rb", GAME_DIR, &to_read);
53 if (!fh.has_value()) return LanguageStrings();
55 auto pos = file.rfind(PATHSEPCHAR);
56 if (pos == std::string::npos) return LanguageStrings();
57 std::string langname = file.substr(pos + 1);
59 /* Check for invalid empty filename */
60 if (langname.empty() || langname.front() == '.') return LanguageStrings();
62 LanguageStrings ret(langname.substr(0, langname.find('.')));
64 char buffer[2048];
65 while (to_read != 0 && fgets(buffer, sizeof(buffer), *fh) != nullptr) {
66 size_t len = strlen(buffer);
68 /* Remove trailing spaces/newlines from the string. */
69 size_t i = len;
70 while (i > 0 && (buffer[i - 1] == '\r' || buffer[i - 1] == '\n' || buffer[i - 1] == ' ')) i--;
71 buffer[i] = '\0';
73 ret.lines.emplace_back(buffer, i);
75 if (len > to_read) {
76 to_read = 0;
77 } else {
78 to_read -= len;
82 return ret;
86 /** A reader that simply reads using fopen. */
87 struct StringListReader : StringReader {
88 StringList::const_iterator p; ///< The current location of the iteration.
89 StringList::const_iterator end; ///< The end of the iteration.
91 /**
92 * Create the reader.
93 * @param data The data to fill during reading.
94 * @param strings The language strings we are reading.
95 * @param master Are we reading the master file?
96 * @param translation Are we reading a translation?
98 StringListReader(StringData &data, const LanguageStrings &strings, bool master, bool translation) :
99 StringReader(data, strings.language.c_str(), master, translation), p(strings.lines.begin()), end(strings.lines.end())
103 std::optional<std::string> ReadLine() override
105 if (this->p == this->end) return std::nullopt;
106 return *this->p++;
110 /** Class for writing an encoded language. */
111 struct TranslationWriter : LanguageWriter {
112 StringList &strings; ///< The encoded strings.
115 * Writer for the encoded data.
116 * @param strings The string table to add the strings to.
118 TranslationWriter(StringList &strings) : strings(strings)
122 void WriteHeader(const LanguagePackHeader *) override
124 /* We don't use the header. */
127 void Finalise() override
129 /* Nothing to do. */
132 void WriteLength(uint) override
134 /* We don't write the length. */
137 void Write(const uint8_t *buffer, size_t length) override
139 this->strings.emplace_back((const char *)buffer, length);
143 /** Class for writing the string IDs. */
144 struct StringNameWriter : HeaderWriter {
145 StringList &strings; ///< The string names.
148 * Writer for the string names.
149 * @param strings The string table to add the strings to.
151 StringNameWriter(StringList &strings) : strings(strings)
155 void WriteStringID(const std::string &name, int stringid) override
157 if (stringid == (int)this->strings.size()) this->strings.emplace_back(name);
160 void Finalise(const StringData &) override
162 /* Nothing to do. */
167 * Scanner to find language files in a GameScript directory.
169 class LanguageScanner : protected FileScanner {
170 private:
171 GameStrings *gs;
172 std::string exclude;
174 public:
175 /** Initialise */
176 LanguageScanner(GameStrings *gs, const std::string &exclude) : gs(gs), exclude(exclude) {}
179 * Scan.
181 void Scan(const std::string &directory)
183 this->FileScanner::Scan(".txt", directory, false);
186 bool AddFile(const std::string &filename, size_t, const std::string &) override
188 if (exclude == filename) return true;
190 auto ls = ReadRawLanguageStrings(filename);
191 if (!ls.IsValid()) return false;
193 gs->raw_strings.push_back(std::move(ls));
194 return true;
199 * Load all translations that we know of.
200 * @return Container with all (compiled) translations.
202 GameStrings *LoadTranslations()
204 const GameInfo *info = Game::GetInfo();
205 assert(info != nullptr);
206 std::string basename(info->GetMainScript());
207 auto e = basename.rfind(PATHSEPCHAR);
208 if (e == std::string::npos) return nullptr;
209 basename.erase(e + 1);
211 std::string filename = basename + "lang" PATHSEP "english.txt";
212 if (!FioCheckFileExists(filename, GAME_DIR)) return nullptr;
214 auto ls = ReadRawLanguageStrings(filename);
215 if (!ls.IsValid()) return nullptr;
217 GameStrings *gs = new GameStrings();
218 try {
219 gs->raw_strings.push_back(std::move(ls));
221 /* Scan for other language files */
222 LanguageScanner scanner(gs, filename);
223 std::string ldir = basename + "lang" PATHSEP;
225 const std::string tar_filename = info->GetTarFile();
226 TarList::iterator iter;
227 if (!tar_filename.empty() && (iter = _tar_list[GAME_DIR].find(tar_filename)) != _tar_list[GAME_DIR].end()) {
228 /* The main script is in a tar file, so find all files that
229 * are in the same tar and add them to the langfile scanner. */
230 for (const auto &tar : _tar_filelist[GAME_DIR]) {
231 /* Not in the same tar. */
232 if (tar.second.tar_filename != iter->first) continue;
234 /* Check the path and extension. */
235 if (tar.first.size() <= ldir.size() || tar.first.compare(0, ldir.size(), ldir) != 0) continue;
236 if (tar.first.compare(tar.first.size() - 4, 4, ".txt") != 0) continue;
238 scanner.AddFile(tar.first, 0, tar_filename);
240 } else {
241 /* Scan filesystem */
242 scanner.Scan(ldir);
245 gs->Compile();
246 return gs;
247 } catch (...) {
248 delete gs;
249 return nullptr;
253 static StringParam::ParamType GetParamType(const CmdStruct *cs)
255 if (cs->value == SCC_RAW_STRING_POINTER) return StringParam::RAW_STRING;
256 if (cs->value == SCC_STRING || cs != TranslateCmdForCompare(cs)) return StringParam::STRING;
257 return StringParam::OTHER;
260 static void ExtractStringParams(const StringData &data, StringParamsList &params)
262 for (size_t i = 0; i < data.max_strings; i++) {
263 const LangString *ls = data.strings[i].get();
265 if (ls != nullptr) {
266 StringParams &param = params.emplace_back();
267 ParsedCommandStruct pcs = ExtractCommandString(ls->english.c_str(), false);
269 for (auto it = pcs.consuming_commands.begin(); it != pcs.consuming_commands.end(); it++) {
270 if (*it == nullptr) {
271 /* Skip empty param unless a non empty param exist after it. */
272 if (std::all_of(it, pcs.consuming_commands.end(), [](auto cs) { return cs == nullptr; })) break;
273 param.emplace_back(StringParam::UNUSED, 1, nullptr);
274 continue;
276 const CmdStruct *cs = *it;
277 param.emplace_back(GetParamType(cs), cs->consumes, cs->cmd);
283 /** Compile the language. */
284 void GameStrings::Compile()
286 StringData data(32);
287 StringListReader master_reader(data, this->raw_strings[0], true, false);
288 master_reader.ParseFile();
289 if (_errors != 0) throw std::exception();
291 this->version = data.Version();
293 ExtractStringParams(data, this->string_params);
295 StringNameWriter id_writer(this->string_names);
296 id_writer.WriteHeader(data);
298 for (const auto &p : this->raw_strings) {
299 data.FreeTranslation();
300 StringListReader translation_reader(data, p, false, p.language != "english");
301 translation_reader.ParseFile();
302 if (_errors != 0) throw std::exception();
304 this->compiled_strings.emplace_back(p.language);
305 TranslationWriter writer(this->compiled_strings.back().lines);
306 writer.WriteLang(data);
310 /** The currently loaded game strings. */
311 GameStrings *_current_data = nullptr;
314 * Get the string pointer of a particular game string.
315 * @param id The ID of the game string.
316 * @return The encoded string.
318 const char *GetGameStringPtr(uint id)
320 if (_current_data == nullptr || _current_data->cur_language == nullptr || id >= _current_data->cur_language->lines.size()) return GetStringPtr(STR_UNDEFINED);
321 return _current_data->cur_language->lines[id].c_str();
325 * Get the string parameters of a particular game string.
326 * @param id The ID of the game string.
327 * @return The string parameters.
329 const StringParams &GetGameStringParams(uint id)
331 /* An empty result for STR_UNDEFINED. */
332 static StringParams empty;
334 if (id >= _current_data->string_params.size()) return empty;
335 return _current_data->string_params[id];
339 * Get the name of a particular game string.
340 * @param id The ID of the game string.
341 * @return The name of the string.
343 const std::string &GetGameStringName(uint id)
345 /* The name for STR_UNDEFINED. */
346 static const std::string undefined = "STR_UNDEFINED";
348 if (id >= _current_data->string_names.size()) return undefined;
349 return _current_data->string_names[id];
353 * Register the current translation to the Squirrel engine.
354 * @param engine The engine to update/
356 void RegisterGameTranslation(Squirrel *engine)
358 delete _current_data;
359 _current_data = LoadTranslations();
360 if (_current_data == nullptr) return;
362 HSQUIRRELVM vm = engine->GetVM();
363 sq_pushroottable(vm);
364 sq_pushstring(vm, "GSText", -1);
365 if (SQ_FAILED(sq_get(vm, -2))) return;
367 int idx = 0;
368 for (const auto &p : _current_data->string_names) {
369 sq_pushstring(vm, p, -1);
370 sq_pushinteger(vm, idx);
371 sq_rawset(vm, -3);
372 idx++;
375 sq_pop(vm, 2);
377 ReconsiderGameScriptLanguage();
381 * Reconsider the game script language, so we use the right one.
383 void ReconsiderGameScriptLanguage()
385 if (_current_data == nullptr) return;
387 std::string language = FS2OTTD(_current_language->file.stem());
388 for (auto &p : _current_data->compiled_strings) {
389 if (p.language == language) {
390 _current_data->cur_language = &p;
391 return;
395 _current_data->cur_language = &_current_data->compiled_strings[0];