Add: INR currency (#8136)
[openttd-github.git] / src / game / game_text.cpp
blob64281c60cb7550de7c3c7d6ba202d73b005dae40
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"
23 #include <stdarg.h>
24 #include <memory>
26 #include "../safeguards.h"
28 void CDECL strgen_warning(const char *s, ...)
30 char buf[1024];
31 va_list va;
32 va_start(va, s);
33 vseprintf(buf, lastof(buf), s, va);
34 va_end(va);
35 DEBUG(script, 0, "%s:%d: warning: %s", _file, _cur_line, buf);
36 _warnings++;
39 void CDECL strgen_error(const char *s, ...)
41 char buf[1024];
42 va_list va;
43 va_start(va, s);
44 vseprintf(buf, lastof(buf), s, va);
45 va_end(va);
46 DEBUG(script, 0, "%s:%d: error: %s", _file, _cur_line, buf);
47 _errors++;
50 void NORETURN CDECL strgen_fatal(const char *s, ...)
52 char buf[1024];
53 va_list va;
54 va_start(va, s);
55 vseprintf(buf, lastof(buf), s, va);
56 va_end(va);
57 DEBUG(script, 0, "%s:%d: FATAL: %s", _file, _cur_line, buf);
58 throw std::exception();
61 /**
62 * Create a new container for language strings.
63 * @param language The language name.
64 * @param end If not nullptr, terminate \a language at this position.
66 LanguageStrings::LanguageStrings(const char *language, const char *end)
68 this->language = stredup(language, end != nullptr ? end - 1 : nullptr);
71 /** Free everything. */
72 LanguageStrings::~LanguageStrings()
74 free(this->language);
77 /**
78 * Read all the raw language strings from the given file.
79 * @param file The file to read from.
80 * @return The raw strings, or nullptr upon error.
82 std::unique_ptr<LanguageStrings> ReadRawLanguageStrings(const char *file)
84 try {
85 size_t to_read;
86 FILE *fh = FioFOpenFile(file, "rb", GAME_DIR, &to_read);
87 if (fh == nullptr) return nullptr;
89 FileCloser fhClose(fh);
91 const char *langname = strrchr(file, PATHSEPCHAR);
92 if (langname == nullptr) {
93 langname = file;
94 } else {
95 langname++;
98 /* Check for invalid empty filename */
99 if (*langname == '.' || *langname == 0) return nullptr;
101 std::unique_ptr<LanguageStrings> ret(new LanguageStrings(langname, strchr(langname, '.')));
103 char buffer[2048];
104 while (to_read != 0 && fgets(buffer, sizeof(buffer), fh) != nullptr) {
105 size_t len = strlen(buffer);
107 /* Remove trailing spaces/newlines from the string. */
108 size_t i = len;
109 while (i > 0 && (buffer[i - 1] == '\r' || buffer[i - 1] == '\n' || buffer[i - 1] == ' ')) i--;
110 buffer[i] = '\0';
112 ret->lines.emplace_back(buffer, i);
114 if (len > to_read) {
115 to_read = 0;
116 } else {
117 to_read -= len;
121 return ret;
122 } catch (...) {
123 return nullptr;
128 /** A reader that simply reads using fopen. */
129 struct StringListReader : StringReader {
130 StringList::const_iterator p; ///< The current location of the iteration.
131 StringList::const_iterator end; ///< The end of the iteration.
134 * Create the reader.
135 * @param data The data to fill during reading.
136 * @param strings The language strings we are reading.
137 * @param master Are we reading the master file?
138 * @param translation Are we reading a translation?
140 StringListReader(StringData &data, const LanguageStrings &strings, bool master, bool translation) :
141 StringReader(data, strings.language, master, translation), p(strings.lines.begin()), end(strings.lines.end())
145 char *ReadLine(char *buffer, const char *last) override
147 if (this->p == this->end) return nullptr;
149 strecpy(buffer, this->p->c_str(), last);
150 this->p++;
152 return buffer;
156 /** Class for writing an encoded language. */
157 struct TranslationWriter : LanguageWriter {
158 StringList &strings; ///< The encoded strings.
161 * Writer for the encoded data.
162 * @param strings The string table to add the strings to.
164 TranslationWriter(StringList &strings) : strings(strings)
168 void WriteHeader(const LanguagePackHeader *header)
170 /* We don't use the header. */
173 void Finalise()
175 /* Nothing to do. */
178 void WriteLength(uint length)
180 /* We don't write the length. */
183 void Write(const byte *buffer, size_t length)
185 this->strings.emplace_back((const char *)buffer, length);
189 /** Class for writing the string IDs. */
190 struct StringNameWriter : HeaderWriter {
191 StringList &strings; ///< The string names.
194 * Writer for the string names.
195 * @param strings The string table to add the strings to.
197 StringNameWriter(StringList &strings) : strings(strings)
201 void WriteStringID(const char *name, int stringid)
203 if (stringid == (int)this->strings.size()) this->strings.emplace_back(name);
206 void Finalise(const StringData &data)
208 /* Nothing to do. */
213 * Scanner to find language files in a GameScript directory.
215 class LanguageScanner : protected FileScanner {
216 private:
217 GameStrings *gs;
218 char *exclude;
220 public:
221 /** Initialise */
222 LanguageScanner(GameStrings *gs, const char *exclude) : gs(gs), exclude(stredup(exclude)) {}
223 ~LanguageScanner() { free(exclude); }
226 * Scan.
228 void Scan(const char *directory)
230 this->FileScanner::Scan(".txt", directory, false);
233 bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename) override
235 if (strcmp(filename, exclude) == 0) return true;
237 auto ls = ReadRawLanguageStrings(filename);
238 if (ls == nullptr) return false;
240 gs->raw_strings.push_back(std::move(ls));
241 return true;
246 * Load all translations that we know of.
247 * @return Container with all (compiled) translations.
249 GameStrings *LoadTranslations()
251 const GameInfo *info = Game::GetInfo();
252 char filename[512];
253 strecpy(filename, info->GetMainScript(), lastof(filename));
254 char *e = strrchr(filename, PATHSEPCHAR);
255 if (e == nullptr) return nullptr;
256 e++; // Make 'e' point after the PATHSEPCHAR
258 strecpy(e, "lang" PATHSEP "english.txt", lastof(filename));
259 if (!FioCheckFileExists(filename, GAME_DIR)) return nullptr;
261 auto ls = ReadRawLanguageStrings(filename);
262 if (ls == nullptr) return nullptr;
264 GameStrings *gs = new GameStrings();
265 try {
266 gs->raw_strings.push_back(std::move(ls));
268 /* Scan for other language files */
269 LanguageScanner scanner(gs, filename);
270 strecpy(e, "lang" PATHSEP, lastof(filename));
271 size_t len = strlen(filename);
273 const char *tar_filename = info->GetTarFile();
274 TarList::iterator iter;
275 if (tar_filename != nullptr && (iter = _tar_list[GAME_DIR].find(tar_filename)) != _tar_list[GAME_DIR].end()) {
276 /* The main script is in a tar file, so find all files that
277 * are in the same tar and add them to the langfile scanner. */
278 TarFileList::iterator tar;
279 FOR_ALL_TARS(tar, GAME_DIR) {
280 /* Not in the same tar. */
281 if (tar->second.tar_filename != iter->first) continue;
283 /* Check the path and extension. */
284 if (tar->first.size() <= len || tar->first.compare(0, len, filename) != 0) continue;
285 if (tar->first.compare(tar->first.size() - 4, 4, ".txt") != 0) continue;
287 scanner.AddFile(tar->first.c_str(), 0, tar_filename);
289 } else {
290 /* Scan filesystem */
291 scanner.Scan(filename);
294 gs->Compile();
295 return gs;
296 } catch (...) {
297 delete gs;
298 return nullptr;
302 /** Compile the language. */
303 void GameStrings::Compile()
305 StringData data(32);
306 StringListReader master_reader(data, *this->raw_strings[0], true, false);
307 master_reader.ParseFile();
308 if (_errors != 0) throw std::exception();
310 this->version = data.Version();
312 StringNameWriter id_writer(this->string_names);
313 id_writer.WriteHeader(data);
315 for (const auto &p : this->raw_strings) {
316 data.FreeTranslation();
317 StringListReader translation_reader(data, *p, false, strcmp(p->language, "english") != 0);
318 translation_reader.ParseFile();
319 if (_errors != 0) throw std::exception();
321 this->compiled_strings.emplace_back(new LanguageStrings(p->language));
322 TranslationWriter writer(this->compiled_strings.back()->lines);
323 writer.WriteLang(data);
327 /** The currently loaded game strings. */
328 GameStrings *_current_data = nullptr;
331 * Get the string pointer of a particular game string.
332 * @param id The ID of the game string.
333 * @return The encoded string.
335 const char *GetGameStringPtr(uint id)
337 if (id >= _current_data->cur_language->lines.size()) return GetStringPtr(STR_UNDEFINED);
338 return _current_data->cur_language->lines[id].c_str();
342 * Register the current translation to the Squirrel engine.
343 * @param engine The engine to update/
345 void RegisterGameTranslation(Squirrel *engine)
347 delete _current_data;
348 _current_data = LoadTranslations();
349 if (_current_data == nullptr) return;
351 HSQUIRRELVM vm = engine->GetVM();
352 sq_pushroottable(vm);
353 sq_pushstring(vm, "GSText", -1);
354 if (SQ_FAILED(sq_get(vm, -2))) return;
356 int idx = 0;
357 for (const auto &p : _current_data->string_names) {
358 sq_pushstring(vm, p.c_str(), -1);
359 sq_pushinteger(vm, idx);
360 sq_rawset(vm, -3);
361 idx++;
364 sq_pop(vm, 2);
366 ReconsiderGameScriptLanguage();
370 * Reconsider the game script language, so we use the right one.
372 void ReconsiderGameScriptLanguage()
374 if (_current_data == nullptr) return;
376 char temp[MAX_PATH];
377 strecpy(temp, _current_language->file, lastof(temp));
379 /* Remove the extension */
380 char *l = strrchr(temp, '.');
381 assert(l != nullptr);
382 *l = '\0';
384 /* Skip the path */
385 char *language = strrchr(temp, PATHSEPCHAR);
386 assert(language != nullptr);
387 language++;
389 for (auto &p : _current_data->compiled_strings) {
390 if (strcmp(p->language, language) == 0) {
391 _current_data->cur_language = p;
392 return;
396 _current_data->cur_language = _current_data->compiled_strings[0];