libgui 2.01
libgui is a GUI library for the GameCube, Wii, and Wii U
 
Loading...
Searching...
No Matches
GuiTextTranslator.cpp
1/****************************************************************************
2 * libgui
3 * Daryl Borth 2009-2026
4 * GuiTextTranslator.cpp
5 ***************************************************************************/
6#include "GuiTextTranslator.h"
7
8#include <cstdlib>
9#include <cstring>
10#include <cstdio>
11
12#define HASHWORDBITS 32
13
14GuiTextTranslator::GuiTextTranslator() :
15 head(nullptr) {
16}
17
18GuiTextTranslator::~GuiTextTranslator() {
19 clear();
20}
21
22/* Defines the so-called `hashpjw' function by P.J. Weinberger */
23uint32_t GuiTextTranslator::hashString(const char *str_param) {
24 uint32_t hval = 0, g;
25 const char *str = str_param;
26
27 while (*str != '\0') {
28 hval <<= 4;
29 hval += (uint8_t) * str++;
30 g = hval & ((uint32_t) 0xf << (HASHWORDBITS - 4));
31 if (g != 0) {
32 hval ^= g >> (HASHWORDBITS - 8);
33 hval ^= g;
34 }
35 }
36 return hval;
37}
38
39/* Expand some escape sequences found in the argument string. */
40char* GuiTextTranslator::expandEscape(const char *str) {
41 char *retval, *rp;
42 const char *cp = str;
43
44 retval = (char*) malloc(strlen(str) + 1);
45 if (retval == nullptr)
46 return nullptr;
47
48 rp = retval;
49
50 while (cp[0] != '\0' && cp[0] != '\\')
51 *rp++ = *cp++;
52
53 if (cp[0] == '\0')
54 goto terminate;
55
56 do {
57 /* Here cp[0] == '\\'. */
58 switch (*++cp) {
59 case '\"':
60 *rp++ = '\"';
61 ++cp;
62 break;
63 case 'a':
64 *rp++ = '\a';
65 ++cp;
66 break;
67 case 'b':
68 *rp++ = '\b';
69 ++cp;
70 break;
71 case 'f':
72 *rp++ = '\f';
73 ++cp;
74 break;
75 case 'n':
76 *rp++ = '\n';
77 ++cp;
78 break;
79 case 'r':
80 *rp++ = '\r';
81 ++cp;
82 break;
83 case 't':
84 *rp++ = '\t';
85 ++cp;
86 break;
87 case 'v':
88 *rp++ = '\v';
89 ++cp;
90 break;
91 case '\\':
92 *rp = '\\';
93 ++cp;
94 break;
95 case '0':
96 case '1':
97 case '2':
98 case '3':
99 case '4':
100 case '5':
101 case '6':
102 case '7': {
103 int ch = *cp++ - '0';
104 if (*cp >= '0' && *cp <= '7') {
105 ch *= 8;
106 ch += *cp++ - '0';
107
108 if (*cp >= '0' && *cp <= '7') {
109 ch *= 8;
110 ch += *cp++ - '0';
111 }
112 }
113 *rp = ch;
114 }
115 break;
116 default:
117 *rp = '\\';
118 break;
119 }
120
121 while (cp[0] != '\0' && cp[0] != '\\')
122 *rp++ = *cp++;
123
124 } while (cp[0] != '\0');
125
126 terminate: *rp = '\0';
127 return retval;
128}
129
130GuiTextTranslator::MSG* GuiTextTranslator::findMessage(uint32_t id) const {
131 for (MSG *msg = head; msg; msg = msg->next) {
132 if (msg->id == id)
133 return msg;
134 }
135 return nullptr;
136}
137
138GuiTextTranslator::MSG* GuiTextTranslator::setMessage(const char *msgid, const char *msgstr) {
139 uint32_t id = hashString(msgid);
140 MSG *msg = findMessage(id);
141
142 if (!msg) {
143 msg = new MSG;
144 msg->id = id;
145 msg->msgstr = nullptr;
146 msg->next = head;
147 head = msg;
148 }
149
150 if (msg) {
151 if (msg->msgstr)
152 free(msg->msgstr);
153
154 if (msgstr)
155 msg->msgstr = expandEscape(msgstr);
156 }
157
158 return msg;
159}
160
162 while (head) {
163 MSG *nextMsg = head->next;
164 if (head->msgstr)
165 free(head->msgstr);
166 delete head;
167 head = nextMsg;
168 }
169}
170
171const char* GuiTextTranslator::memFGets(char *dst, int maxlen, const char *src, const char *eof) const {
172 if (!src || !dst || maxlen <= 0 || src >= eof)
173 return nullptr;
174
175 int len = 0;
176 while (src + len < eof && src[len] != '\n' && len < maxlen - 1) {
177 dst[len] = src[len];
178 len++;
179 }
180 dst[len] = '\0';
181
182 // Advance pointer past the newline if that's what stopped us
183 if (src + len < eof && src[len] == '\n') {
184 return src + len + 1;
185 }
186
187 return src + len;
188}
189
190bool GuiTextTranslator::loadLanguage(const uint8_t *buffer, size_t size) {
191 clear();
192
193 if (!buffer || size == 0)
194 return false;
195
196 char line[1024];
197 char *lastID = nullptr;
198 const char *ptr = reinterpret_cast<const char*>(buffer);
199 const char *eof = ptr + size;
200
201 while (ptr && ptr < eof) {
202 const char *nextPtr = memFGets(line, sizeof(line), ptr, eof);
203
204 if (!nextPtr && ptr == nextPtr)
205 break;
206
207 ptr = nextPtr;
208
209 // Lines starting with # are comments
210 if (line[0] == '#' || line[0] == '\0')
211 continue;
212
213 if (strncmp(line, "msgid \"", 7) == 0) {
214 if (lastID) {
215 free(lastID);
216 lastID = nullptr;
217 }
218
219 char *msgid = &line[7];
220 char *end = strrchr(msgid, '"');
221 if (end && (end - msgid) >= 0) {
222 *end = '\0';
223 lastID = strdup(msgid);
224 }
225 } else if (strncmp(line, "msgstr \"", 8) == 0) {
226 if (!lastID)
227 continue;
228
229 char *msgstr = &line[8];
230 char *end = strrchr(msgstr, '"');
231 if (end && (end - msgstr) >= 0) {
232 *end = '\0';
233 setMessage(lastID, msgstr);
234 }
235
236 free(lastID);
237 lastID = nullptr;
238 }
239 }
240
241 // Clean up if the file ended cleanly but we had an orphaned ID buffered
242 if (lastID)
243 free(lastID);
244
245 return true;
246}
247
248const char* GuiTextTranslator::getText(const char *msgid) const {
249 if (!msgid)
250 return nullptr;
251
252 MSG *msg = findMessage(hashString(msgid));
253
254 if (msg && msg->msgstr && msg->msgstr[0] != '\0') {
255 return msg->msgstr;
256 }
257
258 return msgid;
259}
const char * getText(const char *msgid) const
Retrieves the translated UTF-8 string for a given ASCII message ID.
bool loadLanguage(const uint8_t *buffer, size_t size)
Loads and parses a translation file from a memory buffer.
void clear()
Clears the current translation dictionary and frees memory.