diff options
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rwxr-xr-x | po/compile.py | 55 | ||||
-rw-r--r-- | res/about/lagrange.gmi | 2 | ||||
-rw-r--r-- | res/lang/en.bin | bin | 0 -> 2131 bytes | |||
-rw-r--r-- | src/app.c | 2 | ||||
-rw-r--r-- | src/lang.c | 11 | ||||
-rw-r--r-- | src/ui/labelwidget.c | 36 | ||||
-rw-r--r-- | src/ui/window.c | 4 |
8 files changed, 101 insertions, 10 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 2166acc5..19cc6653 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -73,6 +73,7 @@ set (EMBED_RESOURCES | |||
73 | res/fonts/SourceSansPro-Regular.ttf | 73 | res/fonts/SourceSansPro-Regular.ttf |
74 | res/fonts/SourceSansPro-Bold.ttf | 74 | res/fonts/SourceSansPro-Bold.ttf |
75 | res/fonts/Symbola.ttf | 75 | res/fonts/Symbola.ttf |
76 | res/lang/en.bin | ||
76 | res/shadow.png | 77 | res/shadow.png |
77 | ) | 78 | ) |
78 | if ((UNIX AND NOT APPLE) OR MSYS) | 79 | if ((UNIX AND NOT APPLE) OR MSYS) |
diff --git a/po/compile.py b/po/compile.py new file mode 100755 index 00000000..6e565733 --- /dev/null +++ b/po/compile.py | |||
@@ -0,0 +1,55 @@ | |||
1 | #!/usr/bin/env python3 | ||
2 | # Parses all the .po files and generates binary language strings to be loaded | ||
3 | # at runtime via embedded data. | ||
4 | |||
5 | import os | ||
6 | |||
7 | ESCAPES = { | ||
8 | '\\': '\\', | ||
9 | '"': '"', | ||
10 | 'n': '\n', | ||
11 | 'r': '\r', | ||
12 | 't': '\t' | ||
13 | } | ||
14 | |||
15 | |||
16 | def unquote(string): | ||
17 | txt = string.strip() | ||
18 | if txt[0] != '"' or txt[-1] != '"': | ||
19 | raise Exception("invalid quoted string: " + string) | ||
20 | txt = txt[1:-1] | ||
21 | out = '' | ||
22 | is_escape = False | ||
23 | for c in txt: | ||
24 | if is_escape: | ||
25 | out += ESCAPES[c] | ||
26 | is_escape = False | ||
27 | continue | ||
28 | if c == '\\': | ||
29 | is_escape = True | ||
30 | else: | ||
31 | out += c | ||
32 | return out | ||
33 | |||
34 | |||
35 | messages = [] | ||
36 | for src in os.listdir('.'): | ||
37 | if not src.endswith('.po'): | ||
38 | continue | ||
39 | msg_id, msg_str = None, None | ||
40 | for line in open(src, 'rt', encoding='utf-8').readlines(): | ||
41 | line = line.strip() | ||
42 | if line.startswith('msgid'): | ||
43 | msg_id = unquote(line[6:]) | ||
44 | elif line.startswith('msgstr'): | ||
45 | msg_str = unquote(line[7:]) | ||
46 | messages.append((msg_id, msg_str)) | ||
47 | # Make a binary blob with strings sorted by ID. | ||
48 | compiled = bytes() | ||
49 | for msg in sorted(messages): | ||
50 | compiled += msg[0].encode('utf-8') + bytes([0]) | ||
51 | compiled += msg[1].encode('utf-8') + bytes([0]) | ||
52 | #print(compiled) | ||
53 | open(f'../res/lang/{src[:-3]}.bin', 'wb').write(compiled) | ||
54 | |||
55 | |||
diff --git a/res/about/lagrange.gmi b/res/about/lagrange.gmi index c043d26b..7544c26d 100644 --- a/res/about/lagrange.gmi +++ b/res/about/lagrange.gmi | |||
@@ -10,6 +10,6 @@ o888ooooood8 `Y888""8o `8oooooo. d888b `Y888""8o o888o o888o `8oooooo. `Y8b | |||
10 | "Y88888P' "Y88888P' | 10 | "Y88888P' "Y88888P' |
11 | ``` | 11 | ``` |
12 | # ${about.tagline} | 12 | # ${about.tagline} |
13 | ## ${version} ${APP_VERSION} | 13 | ## ${about.version} ${APP_VERSION} |
14 | => https://skyjake.fi/@jk by @jk@skyjake.fi | 14 | => https://skyjake.fi/@jk by @jk@skyjake.fi |
15 | ${about.powered} | 15 | ${about.powered} |
diff --git a/res/lang/en.bin b/res/lang/en.bin new file mode 100644 index 00000000..d02a2f3d --- /dev/null +++ b/res/lang/en.bin | |||
Binary files differ | |||
@@ -507,7 +507,6 @@ static void communicateWithRunningInstance_App_(iApp *d, iProcessId instance, | |||
507 | 507 | ||
508 | static void init_App_(iApp *d, int argc, char **argv) { | 508 | static void init_App_(iApp *d, int argc, char **argv) { |
509 | init_CommandLine(&d->args, argc, argv); | 509 | init_CommandLine(&d->args, argc, argv); |
510 | init_Lang(); | ||
511 | /* Where was the app started from? We ask SDL first because the command line alone is | 510 | /* Where was the app started from? We ask SDL first because the command line alone is |
512 | not a reliable source of this information, particularly when it comes to different | 511 | not a reliable source of this information, particularly when it comes to different |
513 | operating systems. */ { | 512 | operating systems. */ { |
@@ -533,6 +532,7 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
533 | } | 532 | } |
534 | } | 533 | } |
535 | #endif | 534 | #endif |
535 | init_Lang(); | ||
536 | /* Configure the valid command line options. */ { | 536 | /* Configure the valid command line options. */ { |
537 | defineValues_CommandLine(&d->args, "close-tab", 0); | 537 | defineValues_CommandLine(&d->args, "close-tab", 0); |
538 | defineValues_CommandLine(&d->args, "echo;E", 0); | 538 | defineValues_CommandLine(&d->args, "echo;E", 0); |
@@ -31,15 +31,16 @@ static void clear_Lang_(iLang *d) { | |||
31 | 31 | ||
32 | static void load_Lang_(iLang *d, const char *id) { | 32 | static void load_Lang_(iLang *d, const char *id) { |
33 | /* Load compiled language strings from an embedded blob. */ | 33 | /* Load compiled language strings from an embedded blob. */ |
34 | const iBlock *data = NULL; // &blobLangEn_Embedded; | 34 | iUnused(id); |
35 | const iBlock *data = &blobEn_Embedded; | ||
35 | iMsgStr msg; | 36 | iMsgStr msg; |
36 | for (const char *ptr = constBegin_Block(data); ptr != constEnd_Block(data); ptr++) { | 37 | for (const char *ptr = constBegin_Block(data); ptr != constEnd_Block(data); ptr++) { |
37 | msg.id = ptr; | 38 | msg.id = ptr; |
38 | while (*++ptr) {} | 39 | while (*++ptr) {} |
39 | msg.str = ++ptr; | 40 | msg.str = ++ptr; |
40 | while (*++ptr) {} | 41 | while (*++ptr) {} |
41 | /* Allocate the string. */ | 42 | /* Allocate the string. The data has already been sorted. */ |
42 | insert_SortedArray(d->messages, &msg); | 43 | pushBack_Array(&d->messages->values, &msg); |
43 | } | 44 | } |
44 | } | 45 | } |
45 | 46 | ||
@@ -68,8 +69,8 @@ const char *cstr_Lang(const char *msgId) { | |||
68 | if (locate_SortedArray(d->messages, &key, &pos)) { | 69 | if (locate_SortedArray(d->messages, &key, &pos)) { |
69 | return ((const iMsgStr *) at_SortedArray(d->messages, pos))->str; | 70 | return ((const iMsgStr *) at_SortedArray(d->messages, pos))->str; |
70 | } | 71 | } |
71 | //iAssert(iFalse); | 72 | fprintf(stderr, "[Lang] missing: %s\n", msgId); fflush(stderr); |
72 | fprintf(stderr, "[Lang] missing: %s\n", msgId); | 73 | iAssert(iFalse); |
73 | return msgId; | 74 | return msgId; |
74 | } | 75 | } |
75 | 76 | ||
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c index ea70977c..c3bc4392 100644 --- a/src/ui/labelwidget.c +++ b/src/ui/labelwidget.c | |||
@@ -40,6 +40,7 @@ iLocalDef iInt2 padding_(int64_t flags) { | |||
40 | 40 | ||
41 | struct Impl_LabelWidget { | 41 | struct Impl_LabelWidget { |
42 | iWidget widget; | 42 | iWidget widget; |
43 | iString srcLabel; | ||
43 | iString label; | 44 | iString label; |
44 | int font; | 45 | int font; |
45 | int key; | 46 | int key; |
@@ -357,12 +358,40 @@ void updateSize_LabelWidget(iLabelWidget *d) { | |||
357 | } | 358 | } |
358 | } | 359 | } |
359 | 360 | ||
361 | static void replaceVariables_LabelWidget_(iLabelWidget *d) { | ||
362 | for (const char *label = cstr_String(&d->label); *label; ) { | ||
363 | iRangecc id; | ||
364 | id.start = strstr(label, "${"); | ||
365 | if (!id.start) { | ||
366 | break; | ||
367 | } | ||
368 | id.start += 2; | ||
369 | id.end = strchr(id.start, '}'); | ||
370 | iAssert(id.end != NULL); | ||
371 | /* TODO: Add a lookup that doesn't allocate anything; Lang can handle it. */ | ||
372 | const size_t len = size_Range(&id); | ||
373 | char *key = malloc(len + 1); | ||
374 | memcpy(key, id.start, len); | ||
375 | key[len] = 0; | ||
376 | const char *text = cstr_Lang(key); | ||
377 | const size_t textLen = strlen(text); | ||
378 | free(key); | ||
379 | /* Replace it. */ | ||
380 | size_t startPos = id.start - cstr_String(&d->label) - 2; | ||
381 | remove_Block(&d->label.chars, startPos, len + 3); | ||
382 | insertData_Block(&d->label.chars, startPos, text, textLen); | ||
383 | label = cstr_String(&d->label) + startPos + textLen; | ||
384 | } | ||
385 | } | ||
386 | |||
360 | void init_LabelWidget(iLabelWidget *d, const char *label, const char *cmd) { | 387 | void init_LabelWidget(iLabelWidget *d, const char *label, const char *cmd) { |
361 | init_Widget(&d->widget); | 388 | init_Widget(&d->widget); |
362 | d->font = uiLabel_FontId; | 389 | d->font = uiLabel_FontId; |
363 | d->forceFg = none_ColorId; | 390 | d->forceFg = none_ColorId; |
364 | d->icon = 0; | 391 | d->icon = 0; |
365 | initCStr_String(&d->label, label); | 392 | initCStr_String(&d->srcLabel, label); |
393 | initCopy_String(&d->label, &d->srcLabel); | ||
394 | replaceVariables_LabelWidget_(d); | ||
366 | if (cmd) { | 395 | if (cmd) { |
367 | initCStr_String(&d->command, cmd); | 396 | initCStr_String(&d->command, cmd); |
368 | } | 397 | } |
@@ -381,6 +410,7 @@ void init_LabelWidget(iLabelWidget *d, const char *label, const char *cmd) { | |||
381 | 410 | ||
382 | void deinit_LabelWidget(iLabelWidget *d) { | 411 | void deinit_LabelWidget(iLabelWidget *d) { |
383 | deinit_String(&d->label); | 412 | deinit_String(&d->label); |
413 | deinit_String(&d->srcLabel); | ||
384 | deinit_String(&d->command); | 414 | deinit_String(&d->command); |
385 | } | 415 | } |
386 | 416 | ||
@@ -407,11 +437,15 @@ void setAlignVisually_LabelWidget(iLabelWidget *d, iBool alignVisual) { | |||
407 | 437 | ||
408 | void updateText_LabelWidget(iLabelWidget *d, const iString *text) { | 438 | void updateText_LabelWidget(iLabelWidget *d, const iString *text) { |
409 | set_String(&d->label, text); | 439 | set_String(&d->label, text); |
440 | set_String(&d->srcLabel, text); | ||
441 | replaceVariables_LabelWidget_(d); | ||
410 | refresh_Widget(&d->widget); | 442 | refresh_Widget(&d->widget); |
411 | } | 443 | } |
412 | 444 | ||
413 | void updateTextCStr_LabelWidget(iLabelWidget *d, const char *text) { | 445 | void updateTextCStr_LabelWidget(iLabelWidget *d, const char *text) { |
414 | setCStr_String(&d->label, text); | 446 | setCStr_String(&d->label, text); |
447 | set_String(&d->srcLabel, &d->label); | ||
448 | replaceVariables_LabelWidget_(d); | ||
415 | refresh_Widget(&d->widget); | 449 | refresh_Widget(&d->widget); |
416 | } | 450 | } |
417 | 451 | ||
diff --git a/src/ui/window.c b/src/ui/window.c index b8b2853e..2d1deb72 100644 --- a/src/ui/window.c +++ b/src/ui/window.c | |||
@@ -188,8 +188,8 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
188 | #if !defined (iPlatformAppleMobile) | 188 | #if !defined (iPlatformAppleMobile) |
189 | /* TODO: Submenus wouldn't hurt here. */ | 189 | /* TODO: Submenus wouldn't hurt here. */ |
190 | static const iMenuItem navMenuItems_[] = { | 190 | static const iMenuItem navMenuItems_[] = { |
191 | { add_Icon " ${menu.nav.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, | 191 | { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, |
192 | { "${menu.nav.openlocation}", SDLK_l, KMOD_PRIMARY, "navigate.focus" }, | 192 | { "${menu.openlocation}", SDLK_l, KMOD_PRIMARY, "navigate.focus" }, |
193 | { "---", 0, 0, NULL }, | 193 | { "---", 0, 0, NULL }, |
194 | { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" }, | 194 | { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" }, |
195 | { "${menu.copy.source}", SDLK_c, KMOD_PRIMARY, "copy" }, | 195 | { "${menu.copy.source}", SDLK_c, KMOD_PRIMARY, "copy" }, |