commits
tags
#include "lang.h"
#include "paths.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
/* File-format key for each StringId (see the .lang files under assets/lang). */
static const char *KEY_NAMES[STR_COUNT] = {
[STR_UNTITLED] = "untitled",
[STR_STATUSBAR_LINE] = "statusbar.line",
[STR_STATUSBAR_LINES] = "statusbar.lines",
[STR_QUIT_TITLE] = "quit.title",
[STR_QUIT_SAVE] = "quit.save",
[STR_QUIT_DISCARD] = "quit.discard",
[STR_QUIT_CANCEL] = "quit.cancel",
[STR_QUIT_TITLE_OPEN] = "quit.title_open",
[STR_QUIT_SAVE_OPEN] = "quit.save_open",
[STR_QUIT_DISCARD_OPEN] = "quit.discard_open",
[STR_OPEN_TITLE] = "open.title",
[STR_OPEN_CONFIRM] = "open.confirm",
[STR_OPEN_ERROR] = "open.error",
[STR_HELP_TITLE] = "help.title",
[STR_HELP_ARROWS] = "help.arrows",
[STR_HELP_SHIFT_ARROWS] = "help.shift_arrows",
[STR_HELP_MOUSE_DRAG] = "help.mouse_drag",
[STR_HELP_SAVE] = "help.save",
[STR_HELP_OPEN] = "help.open",
[STR_HELP_COPY_PASTE] = "help.copy_paste",
[STR_HELP_UNDO_REDO] = "help.undo_redo",
[STR_HELP_QUIT] = "help.quit",
[STR_HELP_THIS_HELP] = "help.this_help",
[STR_HELP_ENTER] = "help.enter",
[STR_HELP_BACKSPACE_DELETE] = "help.backspace_delete",
[STR_HELP_HOME_END] = "help.home_end",
[STR_HELP_SOURCE_VIEW] = "help.source_view",
[STR_HELP_ZOOM] = "help.zoom",
[STR_HELP_QUICK_INSERT] = "help.quick_insert",
[STR_HELP_CLOSE] = "help.close",
[STR_SOURCE_HINT] = "source_hint",
[STR_BLOCK_H1] = "block.h1",
[STR_BLOCK_H2] = "block.h2",
[STR_BLOCK_H3] = "block.h3",
[STR_BLOCK_H4] = "block.h4",
[STR_BLOCK_H5] = "block.h5",
[STR_BLOCK_H6] = "block.h6",
[STR_BLOCK_BULLET] = "block.bullet",
[STR_BLOCK_NUMBERED] = "block.numbered",
[STR_BLOCK_QUOTE] = "block.quote",
[STR_BLOCK_CODE] = "block.code",
[STR_BLOCK_TASK] = "block.task",
[STR_BLOCK_HR] = "block.hr",
[STR_BLOCK_IMAGE] = "block.image",
[STR_BLOCK_TABLE] = "block.table",
[STR_QUICK_INSERT_EMPTY] = "quick_insert.empty",
};
/* Built-in fallback, used for any key missing from the loaded language file (including when
no file could be loaded at all). Kept in English as the baseline other translations are
written against. */
static const char *DEFAULTS[STR_COUNT] = {
[STR_UNTITLED] = "Untitled",
[STR_STATUSBAR_LINE] = "line",
[STR_STATUSBAR_LINES] = "lines",
[STR_QUIT_TITLE] = "Save changes before quitting?",
[STR_QUIT_SAVE] = "Save and quit",
[STR_QUIT_DISCARD] = "Quit without saving",
[STR_QUIT_CANCEL] = "Cancel",
[STR_QUIT_TITLE_OPEN] = "Save changes before opening another file?",
[STR_QUIT_SAVE_OPEN] = "Save and open",
[STR_QUIT_DISCARD_OPEN] = "Open without saving",
[STR_OPEN_TITLE] = "Open file",
[STR_OPEN_CONFIRM] = "Open",
[STR_OPEN_ERROR] = "Could not open that file.",
[STR_HELP_TITLE] = "Keyboard shortcuts",
[STR_HELP_ARROWS] = "Arrow keys \xE2\x80\x94 move the cursor",
[STR_HELP_SHIFT_ARROWS] = "Shift + arrow keys \xE2\x80\x94 select text",
[STR_HELP_MOUSE_DRAG] = "Click + drag \xE2\x80\x94 select text",
[STR_HELP_SAVE] = "Ctrl+S \xE2\x80\x94 Save",
[STR_HELP_OPEN] = "Ctrl+O \xE2\x80\x94 Open a file",
[STR_HELP_COPY_PASTE] = "Ctrl+C / Ctrl+V \xE2\x80\x94 Copy / paste",
[STR_HELP_UNDO_REDO] = "Ctrl+Z / Ctrl+Shift+Z \xE2\x80\x94 Undo / redo",
[STR_HELP_QUIT] = "Ctrl+X \xE2\x80\x94 Quit",
[STR_HELP_THIS_HELP] = "F1 \xE2\x80\x94 This help",
[STR_HELP_ENTER] = "Enter \xE2\x80\x94 New line / new block",
[STR_HELP_BACKSPACE_DELETE] = "Backspace / Delete \xE2\x80\x94 Delete a character",
[STR_HELP_HOME_END] = "Home / End \xE2\x80\x94 Start/end of line",
[STR_HELP_SOURCE_VIEW] = "Ctrl+M \xE2\x80\x94 View raw markdown source",
[STR_HELP_ZOOM] = "Ctrl++ / Ctrl+- \xE2\x80\x94 Zoom in / out (Ctrl+0 resets)",
[STR_HELP_QUICK_INSERT] = "\"@\" in an empty paragraph \xE2\x80\x94 insert menu",
[STR_HELP_CLOSE] = "Close",
[STR_SOURCE_HINT] = "Raw markdown source \xE2\x80\x94 read-only \xC2\xB7 Ctrl+M or Esc to go back",
[STR_BLOCK_H1] = "Heading 1",
[STR_BLOCK_H2] = "Heading 2",
[STR_BLOCK_H3] = "Heading 3",
[STR_BLOCK_H4] = "Heading 4",
[STR_BLOCK_H5] = "Heading 5",
[STR_BLOCK_H6] = "Heading 6",
[STR_BLOCK_BULLET] = "Bullet list",
[STR_BLOCK_NUMBERED] = "Numbered list",
[STR_BLOCK_QUOTE] = "Quote",
[STR_BLOCK_CODE] = "Code block",
[STR_BLOCK_TASK] = "Task list",
[STR_BLOCK_HR] = "Horizontal rule",
[STR_BLOCK_IMAGE] = "Image",
[STR_BLOCK_TABLE] = "Table",
[STR_QUICK_INSERT_EMPTY] = "No matches",
};
static char *g_strings[STR_COUNT];
static const char *trim(const char *s, int len, int *outLen) {
while (len > 0 && isspace((unsigned char)s[0])) { s++; len--; }
while (len > 0 && isspace((unsigned char)s[len - 1])) len--;
*outLen = len;
return s;
}
bool lang_parse_line(const char *line, int len) {
int trimmedLen;
const char *t = trim(line, len, &trimmedLen);
if (trimmedLen == 0 || t[0] == '#') return false;
const char *eq = memchr(t, '=', (size_t)trimmedLen);
if (!eq) return false;
int keyLen;
const char *key = trim(t, (int)(eq - t), &keyLen);
int valueLen;
const char *value = trim(eq + 1, (int)(t + trimmedLen - (eq + 1)), &valueLen);
if (keyLen == 0 || valueLen == 0) return false;
for (int i = 0; i < STR_COUNT; i++) {
size_t nameLen = strlen(KEY_NAMES[i]);
if ((size_t)keyLen == nameLen && memcmp(key, KEY_NAMES[i], nameLen) == 0) {
free(g_strings[i]);
g_strings[i] = malloc((size_t)valueLen + 1);
memcpy(g_strings[i], value, (size_t)valueLen);
g_strings[i][valueLen] = '\0';
return true;
}
}
return false;
}
void lang_load(const char *code) {
if (!code || !code[0]) return;
char path[1024];
snprintf(path, sizeof path, "%s/lang/%s.lang", paths_assets_dir(), code);
FILE *f = fopen(path, "rb");
if (!f) return;
char line[1024];
while (fgets(line, sizeof line, f)) {
int len = (int)strlen(line);
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) len--;
lang_parse_line(line, len);
}
fclose(f);
}
void lang_free(void) {
for (int i = 0; i < STR_COUNT; i++) {
free(g_strings[i]);
g_strings[i] = NULL;
}
}
const char *lang_get(StringId id) {
return g_strings[id] ? g_strings[id] : DEFAULTS[id];
}