From 8bd0244a33054cd30f45cfc94fd13244364699da Mon Sep 17 00:00:00 2001 From: Steve Date: Sun, 28 Jan 2018 16:33:37 +0000 Subject: [PATCH] Added i18n stuff. --- common.mk | 2 +- src/common.h | 2 + src/entities/blobs/mia.c | 2 +- src/entities/items/battery.c | 2 +- src/entities/items/cell.c | 2 +- src/entities/items/cherry.c | 2 +- src/entities/items/heart.c | 2 +- src/entities/items/item.c | 10 +- src/entities/items/weaponPickup.c | 6 +- src/entities/structures/cardReader.c | 4 +- src/entities/structures/door.c | 6 +- src/entities/structures/exit.c | 2 +- src/entities/structures/itemPad.c | 4 +- src/entities/structures/lift.c | 2 +- src/entities/structures/powerPoint.c | 2 +- src/entities/structures/teleporter.c | 2 +- src/entities/traps/laserTrap.c | 2 +- src/hub/hub.h | 1 - src/structs.h | 26 +- src/system/i18n.c | 388 +++++++++++++++++++++++++++ src/system/i18n.h | 30 +++ src/world/objectives.c | 4 +- 22 files changed, 472 insertions(+), 31 deletions(-) create mode 100644 src/system/i18n.c create mode 100644 src/system/i18n.h diff --git a/common.mk b/common.mk index 224954d..bbd5bd1 100644 --- a/common.mk +++ b/common.mk @@ -35,7 +35,7 @@ OBJS += effects.o entities.o exit.o explosions.o eyeDroidCommander.o OBJS += fleshChunk.o frost.o OBJS += game.o OBJS += heart.o horizontalDoor.o horizontalLaserTrap.o hub.o hud.o -OBJS += init.o infoPoint.o input.o io.o item.o items.o itemPad.o +OBJS += i18n.o init.o infoPoint.o input.o io.o item.o items.o itemPad.o OBJS += cJSON.o OBJS += key.o keycard.o OBJS += laserTrap.o lift.o lookup.o diff --git a/src/common.h b/src/common.h index a4ea668..e76bb5d 100644 --- a/src/common.h +++ b/src/common.h @@ -28,3 +28,5 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "defs.h" #include "structs.h" + +extern char *getTranslatedString(char *string); diff --git a/src/entities/blobs/mia.c b/src/entities/blobs/mia.c index 3a03b82..057ceed 100644 --- a/src/entities/blobs/mia.c +++ b/src/entities/blobs/mia.c @@ -104,7 +104,7 @@ static void touch(Entity *other) { m->action = preTeleport; m->teleportTimer = FPS * 3; - setGameplayMessage(MSG_OBJECTIVE, "Rescued %s", m->name); + setGameplayMessage(MSG_OBJECTIVE, _("Rescued %s"), m->name); m->isMissionTarget = 0; m->flags |= EF_ALWAYS_PROCESS; playSound(SND_MIA, CH_ANY); diff --git a/src/entities/items/battery.c b/src/entities/items/battery.c index f4c7712..afa8a1c 100644 --- a/src/entities/items/battery.c +++ b/src/entities/items/battery.c @@ -42,7 +42,7 @@ void touch(Entity *other) { world.bob->power = MIN(world.bob->power + i->power, world.bob->powerMax); - setGameplayMessage(MSG_STANDARD, "Picked up a %s", i->name); + setGameplayMessage(MSG_STANDARD, _("Picked up a %s"), i->name); pickupItem(); diff --git a/src/entities/items/cell.c b/src/entities/items/cell.c index 2e6a85f..4ec0c70 100644 --- a/src/entities/items/cell.c +++ b/src/entities/items/cell.c @@ -52,7 +52,7 @@ static void touch(Entity *other) world.bob->power = world.bob->powerMax = game.cells; - setGameplayMessage(MSG_OBJECTIVE, "Found a battery cell - Max power increased!"); + setGameplayMessage(MSG_OBJECTIVE, _("Found a battery cell - Max power increased!")); playSound(SND_HEART_CELL, CH_ITEM); diff --git a/src/entities/items/cherry.c b/src/entities/items/cherry.c index 3d180ac..1941dce 100644 --- a/src/entities/items/cherry.c +++ b/src/entities/items/cherry.c @@ -39,7 +39,7 @@ static void touch(Entity *other) { world.bob->health = limit(world.bob->health + i->value, 0, world.bob->healthMax); - setGameplayMessage(MSG_STANDARD, "Picked up a %s", i->name); + setGameplayMessage(MSG_STANDARD, _("Picked up a %s"), i->name); pickupItem(); diff --git a/src/entities/items/heart.c b/src/entities/items/heart.c index 09d78fa..d85e358 100644 --- a/src/entities/items/heart.c +++ b/src/entities/items/heart.c @@ -64,7 +64,7 @@ static void touch(Entity *other) world.bob->health = world.bob->healthMax = game.hearts; - setGameplayMessage(MSG_OBJECTIVE, "Found a heart - Max health increased!"); + setGameplayMessage(MSG_OBJECTIVE, _("Found a heart - Max health increased!")); playSound(SND_HEART_CELL, CH_ITEM); diff --git a/src/entities/items/item.c b/src/entities/items/item.c index 68719b5..e6b563c 100644 --- a/src/entities/items/item.c +++ b/src/entities/items/item.c @@ -116,13 +116,13 @@ static void bobPickupItem(void) game.keysFound++; updateObjective("KEY"); - setGameplayMessage(MSG_STANDARD, "Picked up a %s", i->name); + setGameplayMessage(MSG_STANDARD, _("Picked up a %s"), i->name); playSound(SND_KEY, CH_ITEM); } else { - setGameplayMessage(MSG_GAMEPLAY, "Can't carry any more keys"); + setGameplayMessage(MSG_GAMEPLAY, _("Can't carry any more keys")); } } else if (i->canBeCarried) @@ -139,13 +139,13 @@ static void bobPickupItem(void) addBobItem(i); - setGameplayMessage(MSG_STANDARD, "Picked up a %s", i->name); + setGameplayMessage(MSG_STANDARD, _("Picked up a %s"), i->name); playSound(SND_ITEM, CH_ITEM); } else { - setGameplayMessage(MSG_GAMEPLAY, "Can't carry any more items"); + setGameplayMessage(MSG_GAMEPLAY, _("Can't carry any more items")); } } else @@ -155,7 +155,7 @@ static void bobPickupItem(void) if (strcmp(world.id, "teeka") != 0) { - setGameplayMessage(MSG_STANDARD, "Picked up a %s", i->name); + setGameplayMessage(MSG_STANDARD, _("Picked up a %s"), i->name); } playSound(SND_ITEM, CH_ITEM); diff --git a/src/entities/items/weaponPickup.c b/src/entities/items/weaponPickup.c index 1906bc9..eb6ac36 100644 --- a/src/entities/items/weaponPickup.c +++ b/src/entities/items/weaponPickup.c @@ -24,7 +24,7 @@ static void (*itemTick)(void); static void tick(void); static void touch(Entity *other); -static char *description[] = { +static char *description[WPN_ANY] = { "Pistol", "Plasma Rifle", "Spread Gun", @@ -86,11 +86,11 @@ static void touch(Entity *other) switch (i->weaponType) { case WPN_GRENADES: - setGameplayMessage(MSG_STANDARD, "Got some Grenades"); + setGameplayMessage(MSG_STANDARD, _("Got some Grenades")); break; default: - setGameplayMessage(MSG_STANDARD, "Picked up a %s", description[i->weaponType]); + setGameplayMessage(MSG_STANDARD, _("Picked up a %s"), _(description[i->weaponType])); break; } diff --git a/src/entities/structures/cardReader.c b/src/entities/structures/cardReader.c index 215c975..e53a42f 100644 --- a/src/entities/structures/cardReader.c +++ b/src/entities/structures/cardReader.c @@ -76,7 +76,7 @@ static void touch(Entity *other) { activateEntities(s->targetNames, 1); - setGameplayMessage(MSG_GAMEPLAY, "%s removed", s->requiredItem); + setGameplayMessage(MSG_GAMEPLAY, _("%s removed"), s->requiredItem); removeItem(s->requiredItem); @@ -89,7 +89,7 @@ static void touch(Entity *other) } else if (s->bobTouching == 0) { - setGameplayMessage(MSG_GAMEPLAY, "%s required", s->requiredItem); + setGameplayMessage(MSG_GAMEPLAY, _("%s required"), s->requiredItem); playSound(SND_DENIED, CH_TOUCH); } diff --git a/src/entities/structures/door.c b/src/entities/structures/door.c index f46d779..20ca975 100644 --- a/src/entities/structures/door.c +++ b/src/entities/structures/door.c @@ -174,7 +174,7 @@ static void touch(Entity *other) } else if (s->thinkTime == 0) { - setGameplayMessage(MSG_GAMEPLAY, "Door is locked"); + setGameplayMessage(MSG_GAMEPLAY, _("Door is locked")); playSound(SND_DENIED, CH_MECHANICAL); } @@ -207,7 +207,7 @@ static void openWithKey(void) { if (s->thinkTime <= 0) { - setGameplayMessage(MSG_GAMEPLAY, "%s required", s->requiredItem); + setGameplayMessage(MSG_GAMEPLAY, _("%s required"), s->requiredItem); playSound(SND_DENIED, CH_MECHANICAL); } @@ -219,7 +219,7 @@ static void openWithKey(void) removeItem(s->requiredItem); - setGameplayMessage(MSG_GAMEPLAY, "%s removed", s->requiredItem); + setGameplayMessage(MSG_GAMEPLAY, _("%s removed"), s->requiredItem); STRNCPY(s->requiredItem, "", MAX_NAME_LENGTH); s->isLocked = 0; diff --git a/src/entities/structures/exit.c b/src/entities/structures/exit.c index 744e03d..bb40b4e 100644 --- a/src/entities/structures/exit.c +++ b/src/entities/structures/exit.c @@ -120,7 +120,7 @@ static void touch(Entity *other) } else { - setGameplayMessage(MSG_GAMEPLAY, "Can't exit yet - required objectives not met"); + setGameplayMessage(MSG_GAMEPLAY, _("Can't exit yet - required objectives not met")); } } diff --git a/src/entities/structures/itemPad.c b/src/entities/structures/itemPad.c index ae2380a..acdf40f 100644 --- a/src/entities/structures/itemPad.c +++ b/src/entities/structures/itemPad.c @@ -81,7 +81,7 @@ static void touch(Entity *other) s->active = 1; - setGameplayMessage(MSG_GAMEPLAY, "%s removed", s->requiredItem); + setGameplayMessage(MSG_GAMEPLAY, _("%s removed"), s->requiredItem); s->sprite[FACING_LEFT] = s->sprite[FACING_RIGHT] = s->sprite[FACING_DIE] = getSpriteIndex("ItemPadActive"); @@ -91,7 +91,7 @@ static void touch(Entity *other) } else if (!s->bobTouching) { - setGameplayMessage(MSG_GAMEPLAY, "%s required", s->requiredItem); + setGameplayMessage(MSG_GAMEPLAY, _("%s required"), s->requiredItem); } s->bobTouching = 2; diff --git a/src/entities/structures/lift.c b/src/entities/structures/lift.c index 8ae00e2..bc4c9ed 100644 --- a/src/entities/structures/lift.c +++ b/src/entities/structures/lift.c @@ -106,7 +106,7 @@ static void activate(int active) if (!isOnScreen(self)) { - setGameplayMessage(MSG_GAMEPLAY, "Lift activated ..."); + setGameplayMessage(MSG_GAMEPLAY, _("Lift activated ...")); } } } diff --git a/src/entities/structures/powerPoint.c b/src/entities/structures/powerPoint.c index b1c6b34..b18e95b 100644 --- a/src/entities/structures/powerPoint.c +++ b/src/entities/structures/powerPoint.c @@ -105,7 +105,7 @@ static void touch(Entity *other) { if (world.bob->power < s->requiredPower && s->bobTouching == 0 && !dev.cheatPower) { - setGameplayMessage(MSG_GAMEPLAY, "Not enough power (%d units required)", s->requiredPower); + setGameplayMessage(MSG_GAMEPLAY, _("Not enough power (%d units required)"), s->requiredPower); } else { diff --git a/src/entities/structures/teleporter.c b/src/entities/structures/teleporter.c index 413a288..69b3e50 100644 --- a/src/entities/structures/teleporter.c +++ b/src/entities/structures/teleporter.c @@ -85,7 +85,7 @@ static void activate(int active) if (!isOnScreen(self)) { - setGameplayMessage(MSG_GAMEPLAY, "Teleporter activated ..."); + setGameplayMessage(MSG_GAMEPLAY, _("Teleporter activated ...")); } } } diff --git a/src/entities/traps/laserTrap.c b/src/entities/traps/laserTrap.c index f3d4cc9..1f92796 100644 --- a/src/entities/traps/laserTrap.c +++ b/src/entities/traps/laserTrap.c @@ -164,7 +164,7 @@ static void activate(int active) if (!isOnScreen(self)) { - setGameplayMessage(MSG_GAMEPLAY, "Lasers disabled ..."); + setGameplayMessage(MSG_GAMEPLAY, _("Lasers disabled ...")); } } } diff --git a/src/hub/hub.h b/src/hub/hub.h index de67c21..33a8817 100644 --- a/src/hub/hub.h +++ b/src/hub/hub.h @@ -23,7 +23,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. extern int getDistance(int x1, int y1, int x2, int y2); extern char *readFile(const char *filename); -extern char *getTranslatedString(char *string); extern Dev dev; extern Game game; diff --git a/src/structs.h b/src/structs.h index a7942c3..01350f5 100644 --- a/src/structs.h +++ b/src/structs.h @@ -30,6 +30,7 @@ typedef struct Tuple Tuple; typedef struct HubMission HubMission; typedef struct Widget Widget; typedef struct Atlas Atlas; +typedef struct Bucket Bucket; typedef struct Entity Entity; typedef struct EntityExt EntityExt; @@ -255,8 +256,8 @@ struct HubMission { char id[MAX_NAME_LENGTH]; char name[MAX_NAME_LENGTH]; char description[MAX_DESCRIPTION_LENGTH]; - float x; - float y; + int x; + int y; int status; int unlockCount; float distance; @@ -451,3 +452,24 @@ struct Atlas { SDL_Rect rect; Atlas *next; }; + +/* ===== i18n stuff ==== */ + +struct Bucket { + char *key, *value; + Bucket *next; +}; + +typedef struct { + Bucket **bucket; + int *bucketCount; +} HashTable; + +typedef struct { + int32_t magicNumber, version, stringCount; + int32_t originalOffset, translationOffset; +} MOHeader; + +typedef struct { + int32_t length, offset; +} MOEntry; diff --git a/src/system/i18n.c b/src/system/i18n.c new file mode 100644 index 0000000..5328afa --- /dev/null +++ b/src/system/i18n.c @@ -0,0 +1,388 @@ +/* +Copyright (C) 2009-2016 Parallel Realities + +This program 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; either version 2 +of the License, or (at your option) any later version. + +This program 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 this program; if not, write to the Free Software +Foundation, 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. +*/ + +#include "i18n.h" + +static HashTable table; + +static int hashCode(char *); +static void put(char *, char *); +static void initTable(void); + +void setLanguage(char *applicationName, char *languageCode) +{ + char language[MAX_LINE_LENGTH], c[MAX_LINE_LENGTH]; + char *lang, **key, **value; + int i, swap; + FILE *fp; + MOHeader header; + MOEntry *original, *translation; + #if DEV == 1 + int read; + #endif + + initTable(); + + language[0] = '\0'; + + if (languageCode == NULL) + { + #ifdef _WIN32 + GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, c, MAX_LINE_LENGTH); + + if (c[0] != '\0') + { + STRNCPY(language, c, MAX_LINE_LENGTH); + + GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, c, MAX_LINE_LENGTH); + + if (c[0] != '\0') + { + strncat(language, "_", MAX_DESCRIPTION_LENGTH - strlen(language) - 1); + + strncat(language, c, MAX_DESCRIPTION_LENGTH - strlen(language) - 1); + } + } + #else + if ((lang = getenv("LC_ALL")) || (lang = getenv("LC_CTYPE")) || (lang = getenv("LANG"))) + { + STRNCPY(language, lang, MAX_LINE_LENGTH); + } + #endif + } + + else + { + STRNCPY(language, languageCode, MAX_LINE_LENGTH); + } + + if (strstr(language, ".") != NULL) + { + lang = strtok(language, "."); + + STRNCPY(language, lang, MAX_LINE_LENGTH); + } + + SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "Locale is %s", language); + + sprintf(c, "%s/%s/LC_MESSAGES/%s.mo", LOCALE_DIR, language, applicationName); + + #if DEV == 1 + printf("Opening %s\n", c); + #endif + + fp = fopen(c, "rb"); + + if (fp == NULL) + { + #if DEV == 1 + printf("Failed to open %s/%s/LC_MESSAGES/%s.mo\n", LOCALE_DIR, language, applicationName); + #endif + + if (strstr(language, "_") == NULL) + { + return; + } + + lang = strtok(language, "_"); + + STRNCPY(language, lang, MAX_LINE_LENGTH); + + sprintf(c, "%s/%s/LC_MESSAGES/%s.mo", LOCALE_DIR, language, applicationName); + + #if DEV == 1 + printf("Opening %s\n", c); + #endif + + fp = fopen(c, "rb"); + + if (fp == NULL) + { + #if DEV == 1 + printf("Failed to open %s/%s/LC_MESSAGES/%s.mo\n", LOCALE_DIR, language, applicationName); + #endif + + return; + } + } + + fread(&header, sizeof(header), 1, fp); + + swap = header.magicNumber == 0x950412de ? 0 : 1; + + if (swap) + { + header.stringCount = SDL_Swap32(header.stringCount); + header.originalOffset = SDL_Swap32(header.originalOffset); + header.translationOffset = SDL_Swap32(header.translationOffset); + } + + original = malloc(sizeof(MOEntry) * header.stringCount); + + translation = malloc(sizeof(MOEntry) * header.stringCount); + + if (original == NULL || translation == NULL) + { + printf("Failed to allocate %d bytes for translation strings\n", (int)sizeof(MOEntry) * header.stringCount); + + exit(1); + } + + #if DEV == 1 + printf("MO file has %d entries\n", header.stringCount); + #endif + + fseek(fp, header.originalOffset, SEEK_SET); + + key = malloc(sizeof(char *) * header.stringCount); + + value = malloc(sizeof(char *) * header.stringCount); + + if (key == NULL || value == NULL) + { + printf("Failed to allocate a whole %d bytes for translation strings\n", (int)sizeof(char *) * header.stringCount); + + exit(1); + } + + for (i=0;inext = NULL; + + table.bucketCount[i] = 0; + } +} + +static void put(char *key, char *value) +{ + Bucket *bucket, *newBucket; + unsigned int hash = hashCode(key); + + #if DEV == 1 + printf("%s = %d\n", key, hash); + #endif + + bucket = table.bucket[hash]; + + while (bucket->next != NULL) + { + bucket = bucket->next; + } + + newBucket = malloc(sizeof(Bucket)); + + if (newBucket == NULL) + { + printf("Failed to allocate a whole %d bytes for a HashTable bucket\n", (int)sizeof(Bucket)); + + exit(1); + } + + newBucket->key = malloc(strlen(key) + 1); + newBucket->value = malloc(strlen(value) + 1); + + if (newBucket->key == NULL || newBucket->value == NULL) + { + printf("Failed to allocate a whole %d bytes for a translation\n", (int)strlen(newBucket->key) + 1); + + exit(1); + } + + STRNCPY(newBucket->key, key, strlen(key) + 1); + STRNCPY(newBucket->value, value, strlen(value) + 1); + + newBucket->next = NULL; + + bucket->next = newBucket; + + table.bucketCount[hash]++; +} + +char *getTranslatedString(char *key) +{ + Bucket *bucket; + unsigned int hash = hashCode(key); + + bucket = table.bucket[hash]->next; + + for (;bucket!=NULL;bucket=bucket->next) + { + if (strcasecmp(key, bucket->key) == 0) + { + return strlen(bucket->value) == 0 ? key : bucket->value; + } + } + + return key; +} + +void cleanupLanguage() +{ + int i; + Bucket *bucket, *p, *q; + + for (i=0;inext;p!=NULL;p=q) + { + free(p->key); + free(p->value); + + q = p->next; + + free(p); + } + + free(bucket); + } + + table.bucket = NULL; +} diff --git a/src/system/i18n.h b/src/system/i18n.h new file mode 100644 index 0000000..d73d04f --- /dev/null +++ b/src/system/i18n.h @@ -0,0 +1,30 @@ +/* +Copyright (C) 2009-2016 Parallel Realities + +This program 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; either version 2 +of the License, or (at your option) any later version. + +This program 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 this program; if not, write to the Free Software +Foundation, 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. +*/ + +#include "../common.h" + +#ifdef _WIN32 + #include +#endif + +#define TABLE_SIZE 255 + +char *getTranslatedString(char *); +void setLanguage(char *, char *); +void cleanupLanguage(void); diff --git a/src/world/objectives.c b/src/world/objectives.c index cc33397..4345ff1 100644 --- a/src/world/objectives.c +++ b/src/world/objectives.c @@ -124,7 +124,7 @@ void updateObjective(char *targetName) { if (o->currentValue == o->targetValue) { - setGameplayMessage(MSG_OBJECTIVE, "%s - Objective Complete!", o->description); + setGameplayMessage(MSG_OBJECTIVE, _("%s - Objective Complete!"), o->description); } else if (o->currentValue < o->targetValue) { @@ -156,7 +156,7 @@ void updateObjective(char *targetName) if (world.allObjectivesComplete) { - setGameplayMessage(MSG_OBJECTIVE, "Mission Complete!"); + setGameplayMessage(MSG_OBJECTIVE, _("Mission Complete!")); } }