2011-12-19 21:05:46 +00:00
|
|
|
/* $Id$ */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This file is part of OpenTTD.
|
|
|
|
* 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.
|
|
|
|
* 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.
|
|
|
|
* 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/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** @file game_text.cpp Implementation of handling translated strings. */
|
|
|
|
|
|
|
|
#include "../stdafx.h"
|
|
|
|
#include "../strgen/strgen.h"
|
|
|
|
#include "../debug.h"
|
|
|
|
#include "../fileio_func.h"
|
2013-03-22 21:21:45 +00:00
|
|
|
#include "../tar_type.h"
|
2011-12-19 21:05:46 +00:00
|
|
|
#include "../script/squirrel_class.hpp"
|
|
|
|
#include "../strings_func.h"
|
|
|
|
#include "game_text.hpp"
|
|
|
|
#include "game.hpp"
|
2013-03-22 21:21:45 +00:00
|
|
|
#include "game_info.hpp"
|
2011-12-19 21:05:46 +00:00
|
|
|
|
|
|
|
#include "table/strings.h"
|
|
|
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
2014-04-23 20:13:33 +00:00
|
|
|
#include "../safeguards.h"
|
|
|
|
|
2011-12-19 21:05:46 +00:00
|
|
|
void CDECL strgen_warning(const char *s, ...)
|
|
|
|
{
|
|
|
|
char buf[1024];
|
|
|
|
va_list va;
|
|
|
|
va_start(va, s);
|
2014-04-24 19:51:45 +00:00
|
|
|
vseprintf(buf, lastof(buf), s, va);
|
2011-12-19 21:05:46 +00:00
|
|
|
va_end(va);
|
|
|
|
DEBUG(script, 0, "%s:%d: warning: %s", _file, _cur_line, buf);
|
|
|
|
_warnings++;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDECL strgen_error(const char *s, ...)
|
|
|
|
{
|
|
|
|
char buf[1024];
|
|
|
|
va_list va;
|
|
|
|
va_start(va, s);
|
2014-04-24 19:51:45 +00:00
|
|
|
vseprintf(buf, lastof(buf), s, va);
|
2011-12-19 21:05:46 +00:00
|
|
|
va_end(va);
|
|
|
|
DEBUG(script, 0, "%s:%d: error: %s", _file, _cur_line, buf);
|
|
|
|
_errors++;
|
|
|
|
}
|
|
|
|
|
|
|
|
void NORETURN CDECL strgen_fatal(const char *s, ...)
|
|
|
|
{
|
|
|
|
char buf[1024];
|
|
|
|
va_list va;
|
|
|
|
va_start(va, s);
|
2014-04-24 19:51:45 +00:00
|
|
|
vseprintf(buf, lastof(buf), s, va);
|
2011-12-19 21:05:46 +00:00
|
|
|
va_end(va);
|
|
|
|
DEBUG(script, 0, "%s:%d: FATAL: %s", _file, _cur_line, buf);
|
|
|
|
throw std::exception();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new container for language strings.
|
|
|
|
* @param language The language name.
|
2013-10-06 12:13:20 +00:00
|
|
|
* @param end If not NULL, terminate \a language at this position.
|
2011-12-19 21:05:46 +00:00
|
|
|
*/
|
2013-10-06 12:13:20 +00:00
|
|
|
LanguageStrings::LanguageStrings(const char *language, const char *end)
|
2011-12-19 21:05:46 +00:00
|
|
|
{
|
2014-04-24 18:58:47 +00:00
|
|
|
this->language = stredup(language, end != NULL ? end - 1 : NULL);
|
2011-12-19 21:05:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Free everything. */
|
|
|
|
LanguageStrings::~LanguageStrings()
|
|
|
|
{
|
|
|
|
free(this->language);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Read all the raw language strings from the given file.
|
|
|
|
* @param file The file to read from.
|
|
|
|
* @return The raw strings, or NULL upon error.
|
|
|
|
*/
|
|
|
|
LanguageStrings *ReadRawLanguageStrings(const char *file)
|
|
|
|
{
|
|
|
|
LanguageStrings *ret = NULL;
|
2013-11-24 15:25:41 +00:00
|
|
|
FILE *fh = NULL;
|
2011-12-19 21:05:46 +00:00
|
|
|
try {
|
|
|
|
size_t to_read;
|
2013-11-24 15:25:41 +00:00
|
|
|
fh = FioFOpenFile(file, "rb", GAME_DIR, &to_read);
|
2011-12-19 21:05:46 +00:00
|
|
|
if (fh == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-10-06 12:13:20 +00:00
|
|
|
const char *langname = strrchr(file, PATHSEPCHAR);
|
|
|
|
if (langname == NULL) {
|
|
|
|
langname = file;
|
|
|
|
} else {
|
|
|
|
langname++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check for invalid empty filename */
|
2013-11-24 15:25:41 +00:00
|
|
|
if (*langname == '.' || *langname == 0) {
|
|
|
|
fclose(fh);
|
|
|
|
return NULL;
|
|
|
|
}
|
2013-10-06 12:13:20 +00:00
|
|
|
|
|
|
|
ret = new LanguageStrings(langname, strchr(langname, '.'));
|
2011-12-19 21:05:46 +00:00
|
|
|
|
|
|
|
char buffer[2048];
|
|
|
|
while (to_read != 0 && fgets(buffer, sizeof(buffer), fh) != NULL) {
|
|
|
|
size_t len = strlen(buffer);
|
|
|
|
|
|
|
|
/* Remove trailing spaces/newlines from the string. */
|
|
|
|
size_t i = len;
|
|
|
|
while (i > 0 && (buffer[i - 1] == '\r' || buffer[i - 1] == '\n' || buffer[i - 1] == ' ')) i--;
|
|
|
|
buffer[i] = '\0';
|
|
|
|
|
2014-04-24 18:37:39 +00:00
|
|
|
*ret->lines.Append() = stredup(buffer, buffer + to_read - 1);
|
2011-12-19 21:05:46 +00:00
|
|
|
|
|
|
|
if (len > to_read) {
|
|
|
|
to_read = 0;
|
|
|
|
} else {
|
|
|
|
to_read -= len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-24 15:25:41 +00:00
|
|
|
fclose(fh);
|
2011-12-19 21:05:46 +00:00
|
|
|
return ret;
|
|
|
|
} catch (...) {
|
2013-11-24 15:25:41 +00:00
|
|
|
if (fh != NULL) fclose(fh);
|
2011-12-19 21:05:46 +00:00
|
|
|
delete ret;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** A reader that simply reads using fopen. */
|
|
|
|
struct StringListReader : StringReader {
|
|
|
|
const char * const *p; ///< The current location of the iteration.
|
|
|
|
const char * const *end; ///< The end of the iteration.
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create the reader.
|
|
|
|
* @param data The data to fill during reading.
|
|
|
|
* @param file The file we are reading.
|
|
|
|
* @param master Are we reading the master file?
|
|
|
|
* @param translation Are we reading a translation?
|
|
|
|
*/
|
|
|
|
StringListReader(StringData &data, const LanguageStrings *strings, bool master, bool translation) :
|
|
|
|
StringReader(data, strings->language, master, translation), p(strings->lines.Begin()), end(strings->lines.End())
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2014-04-25 17:43:09 +00:00
|
|
|
/* virtual */ char *ReadLine(char *buffer, const char *last)
|
2011-12-19 21:05:46 +00:00
|
|
|
{
|
|
|
|
if (this->p == this->end) return NULL;
|
|
|
|
|
2014-04-25 17:43:09 +00:00
|
|
|
strecpy(buffer, *this->p, last);
|
2011-12-19 21:05:46 +00:00
|
|
|
this->p++;
|
|
|
|
|
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/** Class for writing an encoded language. */
|
|
|
|
struct TranslationWriter : LanguageWriter {
|
|
|
|
StringList *strings; ///< The encoded strings.
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Writer for the encoded data.
|
|
|
|
* @param strings The string table to add the strings to.
|
|
|
|
*/
|
|
|
|
TranslationWriter(StringList *strings) : strings(strings)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void WriteHeader(const LanguagePackHeader *header)
|
|
|
|
{
|
|
|
|
/* We don't use the header. */
|
|
|
|
}
|
|
|
|
|
|
|
|
void Finalise()
|
|
|
|
{
|
|
|
|
/* Nothing to do. */
|
|
|
|
}
|
|
|
|
|
|
|
|
void WriteLength(uint length)
|
|
|
|
{
|
|
|
|
/* We don't write the length. */
|
|
|
|
}
|
|
|
|
|
|
|
|
void Write(const byte *buffer, size_t length)
|
|
|
|
{
|
2012-09-09 15:52:49 +00:00
|
|
|
char *dest = MallocT<char>(length + 1);
|
|
|
|
memcpy(dest, buffer, length);
|
|
|
|
dest[length] = '\0';
|
|
|
|
*this->strings->Append() = dest;
|
2011-12-19 21:05:46 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/** Class for writing the string IDs. */
|
|
|
|
struct StringNameWriter : HeaderWriter {
|
|
|
|
StringList *strings; ///< The string names.
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Writer for the string names.
|
|
|
|
* @param strings The string table to add the strings to.
|
|
|
|
*/
|
|
|
|
StringNameWriter(StringList *strings) : strings(strings)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void WriteStringID(const char *name, int stringid)
|
|
|
|
{
|
2014-04-25 15:40:32 +00:00
|
|
|
if (stringid == (int)this->strings->Length()) *this->strings->Append() = stredup(name);
|
2011-12-19 21:05:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Finalise(const StringData &data)
|
|
|
|
{
|
|
|
|
/* Nothing to do. */
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Scanner to find language files in a GameScript directory.
|
|
|
|
*/
|
|
|
|
class LanguageScanner : protected FileScanner {
|
|
|
|
private:
|
|
|
|
GameStrings *gs;
|
|
|
|
char *exclude;
|
|
|
|
|
|
|
|
public:
|
|
|
|
/** Initialise */
|
2014-04-25 15:40:32 +00:00
|
|
|
LanguageScanner(GameStrings *gs, const char *exclude) : gs(gs), exclude(stredup(exclude)) {}
|
2011-12-19 21:05:46 +00:00
|
|
|
~LanguageScanner() { free(exclude); }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Scan.
|
|
|
|
*/
|
|
|
|
void Scan(const char *directory)
|
|
|
|
{
|
|
|
|
this->FileScanner::Scan(".txt", directory, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* virtual */ bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
|
|
|
|
{
|
|
|
|
if (strcmp(filename, exclude) == 0) return true;
|
|
|
|
|
|
|
|
*gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load all translations that we know of.
|
|
|
|
* @return Container with all (compiled) translations.
|
|
|
|
*/
|
|
|
|
GameStrings *LoadTranslations()
|
|
|
|
{
|
2013-03-22 21:21:45 +00:00
|
|
|
const GameInfo *info = Game::GetInfo();
|
|
|
|
char filename[512];
|
|
|
|
strecpy(filename, info->GetMainScript(), lastof(filename));
|
|
|
|
char *e = strrchr(filename, PATHSEPCHAR);
|
|
|
|
if (e == NULL) return NULL;
|
|
|
|
e++; // Make 'e' point after the PATHSEPCHAR
|
|
|
|
|
|
|
|
strecpy(e, "lang" PATHSEP "english.txt", lastof(filename));
|
|
|
|
if (!FioCheckFileExists(filename, GAME_DIR)) return NULL;
|
|
|
|
|
2011-12-19 21:05:46 +00:00
|
|
|
GameStrings *gs = new GameStrings();
|
|
|
|
try {
|
|
|
|
*gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
|
|
|
|
|
|
|
|
/* Scan for other language files */
|
|
|
|
LanguageScanner scanner(gs, filename);
|
2013-03-22 21:21:45 +00:00
|
|
|
strecpy(e, "lang" PATHSEP, lastof(filename));
|
2013-03-24 11:04:58 +00:00
|
|
|
size_t len = strlen(filename);
|
2013-03-22 21:21:45 +00:00
|
|
|
|
|
|
|
const char *tar_filename = info->GetTarFile();
|
|
|
|
TarList::iterator iter;
|
|
|
|
if (tar_filename != NULL && (iter = _tar_list[GAME_DIR].find(tar_filename)) != _tar_list[GAME_DIR].end()) {
|
|
|
|
/* The main script is in a tar file, so find all files that
|
|
|
|
* are in the same tar and add them to the langfile scanner. */
|
|
|
|
TarFileList::iterator tar;
|
|
|
|
FOR_ALL_TARS(tar, GAME_DIR) {
|
|
|
|
/* Not in the same tar. */
|
|
|
|
if (tar->second.tar_filename != iter->first) continue;
|
|
|
|
|
|
|
|
/* Check the path and extension. */
|
|
|
|
if (tar->first.size() <= len || tar->first.compare(0, len, filename) != 0) continue;
|
|
|
|
if (tar->first.compare(tar->first.size() - 4, 4, ".txt") != 0) continue;
|
|
|
|
|
|
|
|
scanner.AddFile(tar->first.c_str(), 0, tar_filename);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* Scan filesystem */
|
|
|
|
scanner.Scan(filename);
|
|
|
|
}
|
2011-12-19 21:05:46 +00:00
|
|
|
|
|
|
|
gs->Compile();
|
|
|
|
return gs;
|
|
|
|
} catch (...) {
|
|
|
|
delete gs;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Compile the language. */
|
|
|
|
void GameStrings::Compile()
|
|
|
|
{
|
|
|
|
StringData data(1);
|
|
|
|
StringListReader master_reader(data, this->raw_strings[0], true, false);
|
|
|
|
master_reader.ParseFile();
|
|
|
|
if (_errors != 0) throw std::exception();
|
|
|
|
|
|
|
|
this->version = data.Version();
|
|
|
|
|
|
|
|
StringNameWriter id_writer(&this->string_names);
|
|
|
|
id_writer.WriteHeader(data);
|
|
|
|
|
|
|
|
for (LanguageStrings **p = this->raw_strings.Begin(); p != this->raw_strings.End(); p++) {
|
|
|
|
data.FreeTranslation();
|
|
|
|
StringListReader translation_reader(data, *p, false, strcmp((*p)->language, "english") != 0);
|
|
|
|
translation_reader.ParseFile();
|
|
|
|
if (_errors != 0) throw std::exception();
|
|
|
|
|
|
|
|
LanguageStrings *compiled = *this->compiled_strings.Append() = new LanguageStrings((*p)->language);
|
|
|
|
TranslationWriter writer(&compiled->lines);
|
|
|
|
writer.WriteLang(data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** The currently loaded game strings. */
|
|
|
|
GameStrings *_current_data = NULL;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the string pointer of a particular game string.
|
|
|
|
* @param id The ID of the game string.
|
|
|
|
* @return The encoded string.
|
|
|
|
*/
|
|
|
|
const char *GetGameStringPtr(uint id)
|
|
|
|
{
|
|
|
|
if (id >= _current_data->cur_language->lines.Length()) return GetStringPtr(STR_UNDEFINED);
|
|
|
|
return _current_data->cur_language->lines[id];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register the current translation to the Squirrel engine.
|
|
|
|
* @param engine The engine to update/
|
|
|
|
*/
|
|
|
|
void RegisterGameTranslation(Squirrel *engine)
|
|
|
|
{
|
|
|
|
delete _current_data;
|
|
|
|
_current_data = LoadTranslations();
|
|
|
|
if (_current_data == NULL) return;
|
|
|
|
|
|
|
|
HSQUIRRELVM vm = engine->GetVM();
|
|
|
|
sq_pushroottable(vm);
|
2014-09-06 17:46:56 +00:00
|
|
|
sq_pushstring(vm, "GSText", -1);
|
2011-12-19 21:05:46 +00:00
|
|
|
if (SQ_FAILED(sq_get(vm, -2))) return;
|
|
|
|
|
|
|
|
int idx = 0;
|
|
|
|
for (const char * const *p = _current_data->string_names.Begin(); p != _current_data->string_names.End(); p++, idx++) {
|
2014-09-06 17:30:33 +00:00
|
|
|
sq_pushstring(vm, *p, -1);
|
2011-12-19 21:05:46 +00:00
|
|
|
sq_pushinteger(vm, idx);
|
|
|
|
sq_rawset(vm, -3);
|
|
|
|
}
|
|
|
|
|
|
|
|
sq_pop(vm, 2);
|
|
|
|
|
|
|
|
ReconsiderGameScriptLanguage();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reconsider the game script language, so we use the right one.
|
|
|
|
*/
|
|
|
|
void ReconsiderGameScriptLanguage()
|
|
|
|
{
|
|
|
|
if (_current_data == NULL) return;
|
|
|
|
|
|
|
|
char temp[MAX_PATH];
|
2014-04-24 19:09:17 +00:00
|
|
|
strecpy(temp, _current_language->file, lastof(temp));
|
2011-12-19 21:05:46 +00:00
|
|
|
|
|
|
|
/* Remove the extension */
|
|
|
|
char *l = strrchr(temp, '.');
|
|
|
|
assert(l != NULL);
|
|
|
|
*l = '\0';
|
|
|
|
|
|
|
|
/* Skip the path */
|
|
|
|
char *language = strrchr(temp, PATHSEPCHAR);
|
|
|
|
assert(language != NULL);
|
|
|
|
language++;
|
|
|
|
|
|
|
|
for (LanguageStrings **p = _current_data->compiled_strings.Begin(); p != _current_data->compiled_strings.End(); p++) {
|
|
|
|
if (strcmp((*p)->language, language) == 0) {
|
|
|
|
_current_data->cur_language = *p;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_current_data->cur_language = _current_data->compiled_strings[0];
|
|
|
|
}
|