~strahinja/poe

c40777e5210834012214e707bdd78bd45de743a9 — Страхиња Радић 9 months ago 5b21322 v1.4.4
Finish WIP: add PoEntry.warning and draw logic

Signed-off-by: Страхиња Радић <contact@strahinja.org>
10 files changed, 247 insertions(+), 149 deletions(-)

M TODO
M config.def.h
M config.h
M draw.c
M po.c
M po.h
M poe.1.in
M poe.c
M util.c
M util.h
M TODO => TODO +3 -3
@@ 1,11 1,11 @@
				      TODO
				      ====

	[~] Investigate newlines loading and saving
	[x] Investigate newlines loading and saving
		[x] Fix line counting when \n at end of string
		[x] Add heuristics to match the ending newline or dot
		[ ] Soft-warn when the number of newlines doesn't match
		    (different color?)
		[x] Soft-warn when the number of newlines doesn't match
		    (Different color? Yes.)

	< > Wrap long lines? Not if it would complicate the code too much.


M config.def.h => config.def.h +73 -60
@@ 1,70 1,83 @@
/* See the file LICENSE for copyright and license details. */

#define SAVE_WRAP_WIDTH 70
#define SAVE_WRAP_WIDTH	  70
#define WRAP_FIRST_MSGSTR 0
#define WARN_COUNT_DOTS	  0

#define DLG_FG				TB_BLACK
#define DLG_BG				TB_WHITE
#define EDIT_MSGID_FG			TB_BLACK | TB_BOLD
#define EDIT_MSGID_BG			TB_WHITE
#define EDIT_MSGID_FOCUS_FG		TB_BLACK
#define EDIT_MSGID_FOCUS_BG		TB_WHITE
#define EDIT_MSGID_HEADING_FG		TB_WHITE | TB_BOLD
#define EDIT_MSGID_HEADING_BG		TB_WHITE
#define EDIT_MSGSTR_FG			TB_BLACK | TB_BOLD
#define EDIT_MSGSTR_BG			TB_BLACK
#define EDIT_MSGSTR_FOCUS_FG		TB_YELLOW | TB_BOLD
#define EDIT_MSGSTR_FOCUS_BG		TB_BLACK
#define ERROR_FG			TB_WHITE | TB_BOLD
#define ERROR_BG			TB_RED
#define FLAG_FG				TB_YELLOW | TB_BOLD
#define FLAG_BG				TB_DEFAULT
#define LIST_FLAGS_FG			TB_YELLOW | TB_BOLD
#define LIST_FLAGS_BG			TB_DEFAULT
#define LIST_FLAGS_SEL_FG		TB_WHITE | TB_BOLD
#define LIST_FLAGS_SEL_BG		TB_BLUE
#define LIST_MSGID_FG			TB_WHITE
#define LIST_MSGID_BG			TB_DEFAULT
#define LIST_MSGID_SEL_FG		TB_WHITE
#define LIST_MSGID_SEL_BG		TB_BLUE
#define LIST_MSGID_SPECIAL_FG		TB_YELLOW | TB_BOLD
#define LIST_MSGID_SPECIAL_BG		TB_DEFAULT
#define LIST_MSGID_SPECIAL_SEL_FG	TB_WHITE | TB_BOLD
#define LIST_MSGID_SPECIAL_SEL_BG	TB_BLUE
#define LIST_MSGSTR_FG			TB_GREEN
#define LIST_MSGSTR_BG			TB_DEFAULT
#define LIST_MSGSTR_SEL_FG		TB_WHITE | TB_BOLD
#define LIST_MSGSTR_SEL_BG		TB_BLUE
#define LIST_SEL_FG			TB_WHITE
#define LIST_SEL_BG			TB_BLUE
#define MSGID_FG			TB_GREEN
#define MSGID_BG			TB_DEFAULT
#define MSGSTR_FG			TB_WHITE
#define MSGSTR_BG			TB_DEFAULT
#define PROMPT_FG			TB_YELLOW | TB_BOLD
#define PROMPT_BG			TB_MAGENTA
#define SEARCH_FG                       TB_BLACK
#define SEARCH_BG                       TB_WHITE
#define SEARCH_EDIT_FG                  TB_WHITE | TB_BOLD
#define SEARCH_EDIT_BG                  TB_BLACK
#define SEARCH_HL_FG                    TB_WHITE | TB_BOLD
#define SEARCH_HL_BG                    TB_GREEN
#define SHADOW_FG			TB_BLACK
#define SHADOW_BG			TB_BLACK | TB_BOLD
#define TITLE_FG			TB_BLACK
#define TITLE_BG			TB_WHITE
#define DLG_FG			  TB_BLACK
#define DLG_BG			  TB_WHITE
#define EDIT_MSGID_FG		  TB_BLACK | TB_BOLD
#define EDIT_MSGID_BG		  TB_WHITE
#define EDIT_MSGID_FOCUS_FG	  TB_BLACK
#define EDIT_MSGID_FOCUS_BG	  TB_WHITE
#define EDIT_MSGID_HEADING_FG	  TB_WHITE | TB_BOLD
#define EDIT_MSGID_HEADING_BG	  TB_WHITE
#define EDIT_MSGSTR_FG		  TB_BLACK | TB_BOLD
#define EDIT_MSGSTR_BG		  TB_BLACK
#define EDIT_MSGSTR_FOCUS_FG	  TB_YELLOW | TB_BOLD
#define EDIT_MSGSTR_FOCUS_BG	  TB_BLACK
#define ERROR_FG		  TB_WHITE | TB_BOLD
#define ERROR_BG		  TB_RED
#define FLAG_FG			  TB_YELLOW | TB_BOLD
#define FLAG_BG			  TB_DEFAULT
#define LIST_FLAGS_FG		  TB_YELLOW | TB_BOLD
#define LIST_FLAGS_BG		  TB_DEFAULT
#define LIST_FLAGS_SEL_FG	  TB_WHITE | TB_BOLD
#define LIST_FLAGS_SEL_BG	  TB_BLUE
#define LIST_MSGID_FG		  TB_WHITE
#define LIST_MSGID_BG		  TB_DEFAULT
#define LIST_MSGID_SEL_FG	  TB_WHITE
#define LIST_MSGID_SEL_BG	  TB_BLUE
#define LIST_MSGID_SPECIAL_FG	  TB_YELLOW | TB_BOLD
#define LIST_MSGID_SPECIAL_BG	  TB_DEFAULT
#define LIST_MSGID_SPECIAL_SEL_FG TB_WHITE | TB_BOLD
#define LIST_MSGID_SPECIAL_SEL_BG TB_BLUE
#define LIST_MSGSTR_FG		  TB_GREEN
#define LIST_MSGSTR_BG		  TB_DEFAULT
#define LIST_MSGSTR_SEL_FG	  TB_WHITE | TB_BOLD
#define LIST_MSGSTR_SEL_BG	  TB_BLUE
#define LIST_MSGSTR_WARN_FG	  TB_RED | TB_BOLD
#define LIST_MSGSTR_WARN_BG	  TB_DEFAULT
#define LIST_MSGSTR_SEL_WARN_FG	  TB_RED | TB_BOLD
#define LIST_MSGSTR_SEL_WARN_BG	  TB_BLUE
#define LIST_SEL_FG		  TB_WHITE
#define LIST_SEL_BG		  TB_BLUE
#define MSGID_FG		  TB_GREEN
#define MSGID_BG		  TB_DEFAULT
#define MSGSTR_FG		  TB_WHITE
#define MSGSTR_BG		  TB_DEFAULT
#define PROMPT_FG		  TB_YELLOW | TB_BOLD
#define PROMPT_BG		  TB_MAGENTA
#define SEARCH_FG		  TB_BLACK
#define SEARCH_BG		  TB_WHITE
#define SEARCH_EDIT_FG		  TB_WHITE | TB_BOLD
#define SEARCH_EDIT_BG		  TB_BLACK
#define SEARCH_HL_FG		  TB_WHITE | TB_BOLD
#define SEARCH_HL_BG		  TB_GREEN
#define SHADOW_FG		  TB_BLACK
#define SHADOW_BG		  TB_BLACK | TB_BOLD
#define TITLE_FG		  TB_BLACK
#define TITLE_BG		  TB_WHITE

void format_filename(char* result, size_t result_size, const char* format,
		const struct DrawState* state);
	const struct DrawState* state);
void format_msgs(char* result, size_t result_size, const char* format,
		const struct DrawState* state);
	const struct DrawState* state);

static const struct StatusSegment status_segments[] = {
	{ .format = "%s%s",		.callback = format_filename, 
	  .fg = TB_BLACK,		.bg = TB_WHITE,	.alignment = LEFT },
	{ .format = "%ld/%ld (%ldu/%ldf/%ldo)",	
					.callback = format_msgs,
	  .fg = TB_WHITE | TB_BOLD,	.bg = TB_BLUE,	.alignment = CENTER },
	{ .format = "H=HELP, q=QUIT",	.callback = NULL,
	  .fg = TB_BLACK,		.bg = TB_WHITE,	.alignment = RIGHT },
	{.format	   = "%s%s",
		.callback  = format_filename,
		.fg	   = TB_BLACK,
		.bg	   = TB_WHITE,
		.alignment = LEFT},
	{.format	   = "%ld/%ld (%ldu/%ldf/%ldo)",
		.callback  = format_msgs,
		.fg	   = TB_WHITE | TB_BOLD,
		.bg	   = TB_BLUE,
		.alignment = CENTER},
	{.format	   = "H=HELP, q=QUIT",
		.callback  = NULL,
		.fg	   = TB_BLACK,
		.bg	   = TB_WHITE,
		.alignment = RIGHT},
};

M config.h => config.h +73 -60
@@ 1,70 1,83 @@
/* See the file LICENSE for copyright and license details. */

#define SAVE_WRAP_WIDTH 70
#define SAVE_WRAP_WIDTH	  70
#define WRAP_FIRST_MSGSTR 0
#define WARN_COUNT_DOTS	  0

#define DLG_FG				TB_BLACK
#define DLG_BG				TB_WHITE
#define EDIT_MSGID_FG			TB_BLACK | TB_BOLD
#define EDIT_MSGID_BG			TB_WHITE
#define EDIT_MSGID_FOCUS_FG		TB_BLACK
#define EDIT_MSGID_FOCUS_BG		TB_WHITE
#define EDIT_MSGID_HEADING_FG		TB_WHITE | TB_BOLD
#define EDIT_MSGID_HEADING_BG		TB_WHITE
#define EDIT_MSGSTR_FG			TB_BLACK | TB_BOLD
#define EDIT_MSGSTR_BG			TB_BLACK
#define EDIT_MSGSTR_FOCUS_FG		TB_YELLOW | TB_BOLD
#define EDIT_MSGSTR_FOCUS_BG		TB_BLACK
#define ERROR_FG			TB_WHITE | TB_BOLD
#define ERROR_BG			TB_RED
#define FLAG_FG				TB_YELLOW | TB_BOLD
#define FLAG_BG				TB_DEFAULT
#define LIST_FLAGS_FG			TB_YELLOW | TB_BOLD
#define LIST_FLAGS_BG			TB_DEFAULT
#define LIST_FLAGS_SEL_FG		TB_WHITE | TB_BOLD
#define LIST_FLAGS_SEL_BG		TB_BLUE
#define LIST_MSGID_FG			TB_WHITE
#define LIST_MSGID_BG			TB_DEFAULT
#define LIST_MSGID_SEL_FG		TB_WHITE
#define LIST_MSGID_SEL_BG		TB_BLUE
#define LIST_MSGID_SPECIAL_FG		TB_YELLOW | TB_BOLD
#define LIST_MSGID_SPECIAL_BG		TB_DEFAULT
#define LIST_MSGID_SPECIAL_SEL_FG	TB_WHITE | TB_BOLD
#define LIST_MSGID_SPECIAL_SEL_BG	TB_BLUE
#define LIST_MSGSTR_FG			TB_GREEN
#define LIST_MSGSTR_BG			TB_DEFAULT
#define LIST_MSGSTR_SEL_FG		TB_WHITE | TB_BOLD
#define LIST_MSGSTR_SEL_BG		TB_BLUE
#define LIST_SEL_FG			TB_WHITE
#define LIST_SEL_BG			TB_BLUE
#define MSGID_FG			TB_GREEN
#define MSGID_BG			TB_DEFAULT
#define MSGSTR_FG			TB_WHITE
#define MSGSTR_BG			TB_DEFAULT
#define PROMPT_FG			TB_YELLOW | TB_BOLD
#define PROMPT_BG			TB_MAGENTA
#define SEARCH_FG                       TB_BLACK
#define SEARCH_BG                       TB_WHITE
#define SEARCH_EDIT_FG                  TB_WHITE | TB_BOLD
#define SEARCH_EDIT_BG                  TB_BLACK
#define SEARCH_HL_FG                    TB_WHITE | TB_BOLD
#define SEARCH_HL_BG                    TB_GREEN
#define SHADOW_FG			TB_BLACK
#define SHADOW_BG			TB_BLACK | TB_BOLD
#define TITLE_FG			TB_BLACK
#define TITLE_BG			TB_WHITE
#define DLG_FG			  TB_BLACK
#define DLG_BG			  TB_WHITE
#define EDIT_MSGID_FG		  TB_BLACK | TB_BOLD
#define EDIT_MSGID_BG		  TB_WHITE
#define EDIT_MSGID_FOCUS_FG	  TB_BLACK
#define EDIT_MSGID_FOCUS_BG	  TB_WHITE
#define EDIT_MSGID_HEADING_FG	  TB_WHITE | TB_BOLD
#define EDIT_MSGID_HEADING_BG	  TB_WHITE
#define EDIT_MSGSTR_FG		  TB_BLACK | TB_BOLD
#define EDIT_MSGSTR_BG		  TB_BLACK
#define EDIT_MSGSTR_FOCUS_FG	  TB_YELLOW | TB_BOLD
#define EDIT_MSGSTR_FOCUS_BG	  TB_BLACK
#define ERROR_FG		  TB_WHITE | TB_BOLD
#define ERROR_BG		  TB_RED
#define FLAG_FG			  TB_YELLOW | TB_BOLD
#define FLAG_BG			  TB_DEFAULT
#define LIST_FLAGS_FG		  TB_YELLOW | TB_BOLD
#define LIST_FLAGS_BG		  TB_DEFAULT
#define LIST_FLAGS_SEL_FG	  TB_WHITE | TB_BOLD
#define LIST_FLAGS_SEL_BG	  TB_BLUE
#define LIST_MSGID_FG		  TB_WHITE
#define LIST_MSGID_BG		  TB_DEFAULT
#define LIST_MSGID_SEL_FG	  TB_WHITE
#define LIST_MSGID_SEL_BG	  TB_BLUE
#define LIST_MSGID_SPECIAL_FG	  TB_YELLOW | TB_BOLD
#define LIST_MSGID_SPECIAL_BG	  TB_DEFAULT
#define LIST_MSGID_SPECIAL_SEL_FG TB_WHITE | TB_BOLD
#define LIST_MSGID_SPECIAL_SEL_BG TB_BLUE
#define LIST_MSGSTR_FG		  TB_GREEN
#define LIST_MSGSTR_BG		  TB_DEFAULT
#define LIST_MSGSTR_SEL_FG	  TB_WHITE | TB_BOLD
#define LIST_MSGSTR_SEL_BG	  TB_BLUE
#define LIST_MSGSTR_WARN_FG	  TB_RED | TB_BOLD
#define LIST_MSGSTR_WARN_BG	  TB_DEFAULT
#define LIST_MSGSTR_SEL_WARN_FG	  TB_RED | TB_BOLD
#define LIST_MSGSTR_SEL_WARN_BG	  TB_BLUE
#define LIST_SEL_FG		  TB_WHITE
#define LIST_SEL_BG		  TB_BLUE
#define MSGID_FG		  TB_GREEN
#define MSGID_BG		  TB_DEFAULT
#define MSGSTR_FG		  TB_WHITE
#define MSGSTR_BG		  TB_DEFAULT
#define PROMPT_FG		  TB_YELLOW | TB_BOLD
#define PROMPT_BG		  TB_MAGENTA
#define SEARCH_FG		  TB_BLACK
#define SEARCH_BG		  TB_WHITE
#define SEARCH_EDIT_FG		  TB_WHITE | TB_BOLD
#define SEARCH_EDIT_BG		  TB_BLACK
#define SEARCH_HL_FG		  TB_WHITE | TB_BOLD
#define SEARCH_HL_BG		  TB_GREEN
#define SHADOW_FG		  TB_BLACK
#define SHADOW_BG		  TB_BLACK | TB_BOLD
#define TITLE_FG		  TB_BLACK
#define TITLE_BG		  TB_WHITE

void format_filename(char* result, size_t result_size, const char* format,
		const struct DrawState* state);
	const struct DrawState* state);
void format_msgs(char* result, size_t result_size, const char* format,
		const struct DrawState* state);
	const struct DrawState* state);

static const struct StatusSegment status_segments[] = {
	{ .format = "%s%s",		.callback = format_filename, 
	  .fg = TB_BLACK,		.bg = TB_WHITE,	.alignment = LEFT },
	{ .format = "%ld/%ld (%ldu/%ldf/%ldo)",	
					.callback = format_msgs,
	  .fg = TB_WHITE | TB_BOLD,	.bg = TB_BLUE,	.alignment = CENTER },
	{ .format = "H=HELP, q=QUIT",	.callback = NULL,
	  .fg = TB_BLACK,		.bg = TB_WHITE,	.alignment = RIGHT },
	{.format	   = "%s%s",
		.callback  = format_filename,
		.fg	   = TB_BLACK,
		.bg	   = TB_WHITE,
		.alignment = LEFT},
	{.format	   = "%ld/%ld (%ldu/%ldf/%ldo)",
		.callback  = format_msgs,
		.fg	   = TB_WHITE | TB_BOLD,
		.bg	   = TB_BLUE,
		.alignment = CENTER},
	{.format	   = "H=HELP, q=QUIT",
		.callback  = NULL,
		.fg	   = TB_BLACK,
		.bg	   = TB_WHITE,
		.alignment = RIGHT},
};

M draw.c => draw.c +15 -12
@@ 826,18 826,21 @@ draw_list_visible(const struct DrawState* state)
		/* don't use msgstr_index as then the entire list will shift
		 * when changing currently displayed plural form msgstr in an
		 * edit box */
		if (current->msgstr && current->msgstr_count > 0
			&& current->msgstr + state->msgstr_index)
			u32_draw_string(state->maxx/2, cy, 
					sel ? LIST_MSGSTR_SEL_FG 
						: LIST_MSGSTR_FG,
					sel ? LIST_MSGSTR_SEL_BG 
						: LIST_MSGSTR_BG,
					*(current->msgstr /*+ 
						state->msgstr_index*/), 
					state->maxx/2, state->maxx/2, 2, 2,
					LEFT, 0, 1, state->search, SEARCH_HL_FG,
					SEARCH_HL_BG);
		if (current->msgstr && current->msgstr_count > 0)
			u32_draw_string(state->maxx / 2, cy,
				current->warning
					? (sel ? LIST_MSGSTR_SEL_WARN_FG
					       : LIST_MSGSTR_WARN_FG)
					: (sel ? LIST_MSGSTR_SEL_FG
					       : LIST_MSGSTR_FG),
				current->warning
					? (sel ? LIST_MSGSTR_SEL_WARN_BG
					       : LIST_MSGSTR_WARN_BG)
					: (sel ? LIST_MSGSTR_SEL_BG
					       : LIST_MSGSTR_BG),
				*(current->msgstr), state->maxx / 2,
				state->maxx / 2, 2, 2, LEFT, 0, 1,
				state->search, SEARCH_HL_FG, SEARCH_HL_BG);
		i++;
		cy++;
	}

M po.c => po.c +26 -0
@@ 29,6 29,7 @@ init_po_entry(struct PoEntry* entry)
{
	entry->flags		   = FL_NONE;
	entry->obsolete		   = 0;
	entry->warning		   = 0;
	entry->msgid		   = NULL;
	entry->msgid_size	   = 0;
	entry->msgid_len	   = 0;


@@ 399,6 400,7 @@ load_file(struct DrawState* dstate, long* lineno, long* col)
{
	struct PoEntry* newchunk = calloc(ALLOC_DELTA, sizeof(struct PoEntry));
	struct PoEntry* entry	 = NULL;
	struct PoEntry* current	 = NULL;
	ParseState pstate	 = PS_NONE;
	int has_plural_msgid	 = 0;



@@ 479,6 481,15 @@ load_file(struct DrawState* dstate, long* lineno, long* col)
	update_statistics(dstate->entries, dstate, entry);
	dstate->real_msgid_count = dstate->msgid_count + dstate->obsolete_count;

	current = dstate->entries;
	while (current != dstate->entries + dstate->real_msgid_count)
	{
		if (!current->obsolete)
			update_warning(current->msgid, *current->msgstr,
				&current->warning);
		current++;
	}

	return LOAD_ERR_NONE;
}



@@ 1194,3 1205,18 @@ update_statistics(const struct PoEntry* entries, struct DrawState* state,
			current++;
	}
}

void
update_warning(const uint32_t* msgid, const uint32_t* msgstr, int* warning)
{
	if (!msgid || !msgstr || !warning)
		return;
	if (u32_count_strings(msgid, (const uint32_t*)L"\\n")
			!= u32_count_strings(msgstr, (const uint32_t*)L"\\n")
		|| (WARN_COUNT_DOTS
			&& u32_count_chars(msgid, '.')
				!= u32_count_chars(msgstr, '.')))
		*warning = 1;
	else
		*warning = 0;
}

M po.h => po.h +2 -0
@@ 69,6 69,7 @@ typedef enum {
struct PoEntry {
	Flags flags;
	int obsolete;
	int warning;
	uint32_t* msgid;
	size_t msgid_size;
	size_t msgid_len;


@@ 119,3 120,4 @@ int parse_po_line(const char* line, struct DrawState* dstate,
	int* msgstr_index);
void update_statistics(const struct PoEntry* entries, struct DrawState* state,
	struct PoEntry* entry);
void update_warning(const uint32_t* msgid, const uint32_t* msgstr, int* warning);

M poe.1.in => poe.1.in +17 -0
@@ 167,6 167,23 @@ _
.
.sp 1
.
.SH "ERROR CHECKING"
.
.LP
.B poe
has some rudimentary checks of the translated messages built in. First, when
saving changes in the edit box, if the msgid has a newline character
(\fC\\n\fP), a dot (\fC.\fP) or a space (\fC\~\fP) at the end, and the
corresponding msgstr doesn't, msgstr being saved will have its ending character
made to match the one from the msgid.
.
.PP
Second, if the numbers of newline characters (\fC\\n\fP) in msgid and msgstr
don't match, msgstr will be shown in a different color on the main screen. If
the number of newlines was intended, you can simply ignore this warning.
Otherwise, it can be useful to detect unwanted discrepancies in formatting
between the original message and the translation.
.
.SH AUTHOR
.
Strahinya Radich,

M poe.c => poe.c +8 -6
@@ 468,8 468,9 @@ load_msgstr(struct DrawState* state)
void
save_msgstr(struct DrawState* state)
{
	uint32_t* to_save   = NULL;
	size_t to_save_size = 0;
	uint32_t* to_save	= NULL;
	size_t to_save_size	= 0;
	struct PoEntry* current = NULL;

	state->show_edit	 = 0;
	state->edit_info_focused = 0;


@@ 510,10 511,11 @@ save_msgstr(struct DrawState* state)
	state->info_rows_count = 0;
	state->info_buffer     = NULL;
	u32_encode_tabs(to_save, to_save_size * sizeof(uint32_t));
	u32_match_msgid_ending(state->entries[state->msgid_number - 1].msgid, 
			to_save, to_save_size * sizeof(uint32_t));
	u32_set_msgstr(&state->entries[state->msgid_number - 1], to_save,
		state->msgstr_index, 0, state);
	current = &state->entries[state->msgid_number - 1];
	u32_match_msgid_ending(current->msgid, to_save,
		to_save_size * sizeof(uint32_t));
	u32_set_msgstr(current, to_save, state->msgstr_index, 0, state);
	update_warning(current->msgid, to_save, &current->warning);
	state->input_changed_fuzzy = UNCHANGED;

	tb_set_cursor(TB_HIDE_CURSOR, TB_HIDE_CURSOR);

M util.c => util.c +28 -7
@@ 94,14 94,12 @@ u32_match_msgid_ending(const uint32_t* msgid, uint32_t* msgstr, size_t max)
		}
	}
	else if (buf_len > 1
		&& u32_starts_with(buf + buf_len - 2, 
			(uint32_t*)L"\\n"))
		&& u32_starts_with(buf + buf_len - 2, (uint32_t*)L"\\n"))
	{
		buf_len -= 2;
		buf[buf_len] = 0;
	}
	else if (buf_len > 0
		&& u32_strstr((uint32_t*)L" .", buf + buf_len - 1))
	else if (buf_len > 0 && u32_strstr((uint32_t*)L" .", buf + buf_len - 1))
	{
		buf_len--;
		buf[buf_len] = 0;


@@ 113,10 111,10 @@ u32_match_msgid_ending(const uint32_t* msgid, uint32_t* msgstr, size_t max)
}

size_t
u32_num_chars(uint32_t* s, uint32_t ch)
u32_count_chars(const uint32_t* s, const uint32_t ch)
{
	size_t result = 0;
	uint32_t* ps  = s;
	size_t result	   = 0;
	const uint32_t* ps = s;
	while (*ps)
	{
		if (*ps == ch)


@@ 127,6 125,29 @@ u32_num_chars(uint32_t* s, uint32_t ch)
}

size_t
u32_count_strings(const uint32_t* haystack, const uint32_t* needle)
{
	size_t result		  = 0;
	const uint32_t* phaystack = haystack;
	size_t needle_len	  = 0;

	if (!haystack || !needle)
		return 0;

	needle_len = u32_strlen(needle);
	if (!needle_len)
		return 0;

	while (*phaystack)
	{
		if (u32_starts_with(phaystack, needle))
			result++;
		phaystack++;
	}
	return result;
}

size_t
u32_encode_tabs(uint32_t* s, size_t max)
{
	uint32_t* buf = calloc(max + 1, sizeof(uint32_t));

M util.h => util.h +2 -1
@@ 31,7 31,8 @@ size_t u8_string_to_unicode(uint32_t* us, const char* s, const size_t max);
size_t unicode_string_to_u8(char* s, const uint32_t* us, const size_t max);
size_t u32_match_msgid_ending(const uint32_t* msgid, uint32_t* msgstr,
	size_t max);
size_t u32_num_chars(uint32_t* s, uint32_t ch);
size_t u32_count_chars(const uint32_t* s, const uint32_t ch);
size_t u32_count_strings(const uint32_t* haystack, const uint32_t* needle);
size_t u32_encode_tabs(uint32_t* s, size_t max);
size_t u32_decode_tabs(uint32_t* s, size_t max);
size_t u32_strlen(const uint32_t* s);