File 0001-msgcat-Add-feature-to-use-the-newest-po-file.patch of Package gettext-runtime
From b634e104d13117186d2f7d68d867273215a9cec6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mark=C3=A9ta=20Cal=C3=A1bkov=C3=A1?=
<meggy.calabkova@gmail.com>
Date: Tue, 3 Feb 2026 14:49:59 +0100
Subject: [PATCH] msgcat: Add feature to use the newest po file.
When concatenating po files, it is often useful to prefer strings from
the newest file regardless their ordering on the command line.
Updated to 1.0
---
gettext-tools/src/Makefile.am | 3 +-
gettext-tools/src/message.c | 8 +++
gettext-tools/src/message.h | 5 +-
gettext-tools/src/msgcat.c | 10 ++++
gettext-tools/src/msgl-age.c | 96 +++++++++++++++++++++++++++++++++
gettext-tools/src/msgl-age.h | 36 +++++++++++++
gettext-tools/src/msgl-cat.c | 15 ++++++
gettext-tools/src/msgl-cat.h | 1 +
gettext-tools/src/msgl-header.c | 46 ++++++++++++++++
gettext-tools/src/msgl-header.h | 6 +++
10 files changed, 224 insertions(+), 2 deletions(-)
create mode 100644 gettext-tools/src/msgl-age.c
create mode 100644 gettext-tools/src/msgl-age.h
diff --git a/gettext-tools/src/Makefile.am b/gettext-tools/src/Makefile.am
index 1e6ae7f..2892337 100644
--- a/gettext-tools/src/Makefile.am
+++ b/gettext-tools/src/Makefile.am
@@ -62,7 +62,7 @@ noinst_HEADERS = \
write-catalog.h write-po.h write-properties.h write-stringtable.h \
dir-list.h file-list.h read-po-gram.h cldr-plural.h \
cldr-plural-exp.h locating-rules.h its.h search-path.h \
- msgl-charset.h msgl-equal.h msgl-iconv.h msgl-ascii.h msgl-ofn.h msgl-cat.h \
+ msgl-age.h msgl-charset.h msgl-equal.h msgl-iconv.h msgl-ascii.h msgl-ofn.h msgl-cat.h \
msgl-header.h msgl-english.h msgl-check.h msgl-fsearch.h msgl-merge.h \
msgfmt.h msgunfmt.h \
plural-count.h plural-eval.h plural-distrib.h \
@@ -241,6 +241,7 @@ libgettextsrc_la_SOURCES = \
read-catalog-special.c \
read-catalog.c read-catalog-file.c \
write-catalog.c write-properties.c write-stringtable.c write-po.c \
+ msgl-age.c \
msgl-ascii.c \
msgl-ofn.c \
msgl-iconv.c \
diff --git a/gettext-tools/src/message.c b/gettext-tools/src/message.c
index 4b7fc8b..4ff9c0c 100644
--- a/gettext-tools/src/message.c
+++ b/gettext-tools/src/message.c
@@ -404,6 +404,14 @@ message_list_append (message_list_ty *mlp, message_ty *mp)
}
+void
+message_list_append_list (message_list_ty *mlp, message_list_ty *mlp2)
+{
+ for (int i = 0; i < mlp2->nitems; i++)
+ message_list_append (mlp,mlp2->item[i]);
+}
+
+
void
message_list_prepend (message_list_ty *mlp, message_ty *mp)
{
diff --git a/gettext-tools/src/message.h b/gettext-tools/src/message.h
index b73b0fc..ae0fb0e 100644
--- a/gettext-tools/src/message.h
+++ b/gettext-tools/src/message.h
@@ -24,7 +24,7 @@
#include "mem-hash-map.h"
#include <stdbool.h>
-
+#include <time.h>
#ifdef __cplusplus
extern "C" {
@@ -283,6 +283,8 @@ extern void
message_list_free (message_list_ty *mlp, int keep_messages);
extern void
message_list_append (message_list_ty *mlp, message_ty *mp);
+extern void
+ message_list_append_list (message_list_ty *mlp, message_list_ty *mlp2);
extern void
message_list_prepend (message_list_ty *mlp, message_ty *mp);
extern void
@@ -365,6 +367,7 @@ struct msgdomain_list_ty
size_t nitems_max;
bool use_hashtable;
const char *encoding; /* canonicalized encoding or NULL if unknown */
+ time_t msgage;
};
extern msgdomain_list_ty *
diff --git a/gettext-tools/src/msgcat.c b/gettext-tools/src/msgcat.c
index 6383e89..b6441e5 100644
--- a/gettext-tools/src/msgcat.c
+++ b/gettext-tools/src/msgcat.c
@@ -94,6 +94,7 @@ main (int argc, char **argv)
more_than = 0;
less_than = INT_MAX;
use_first = false;
+ use_newest = false;
catalog_input_format_ty input_syntax = &input_format_po;
catalog_output_format_ty output_syntax = &output_format_po;
bool sort_by_msgid = false;
@@ -130,6 +131,7 @@ main (int argc, char **argv)
{ "to-code", 't', required_argument },
{ "unique", 'u', no_argument },
{ "use-first", CHAR_MAX + 1, no_argument },
+ { "use-newest", CHAR_MAX + 10, no_argument },
{ "version", 'V', no_argument },
{ "width", 'w', required_argument },
{ "more-than", '>', required_argument },
@@ -271,6 +273,11 @@ main (int argc, char **argv)
message_print_style_filepos (filepos_comment_none);
break;
+ case CHAR_MAX + 10: /* --use-newest */
+ use_newest = true;
+ use_first = true;
+ break;
+
default:
usage (EXIT_FAILURE);
/* NOTREACHED */
@@ -424,6 +431,9 @@ Output details:\n"));
printf (_("\
--use-first use first available translation for each\n\
message, don't merge several translations\n"));
+ printf (_("\
+ --use-newest use the most up-to-date available translation\n\
+ for each message, don't merge several translations\n"));
printf (_("\
--lang=CATALOGNAME set 'Language' field in the header entry\n"));
printf (_("\
diff --git a/gettext-tools/src/msgl-age.c b/gettext-tools/src/msgl-age.c
new file mode 100644
index 0000000..6a902fa
--- /dev/null
+++ b/gettext-tools/src/msgl-age.c
@@ -0,0 +1,96 @@
+/* Message list header time simple parser.
+ Copyright (C) 2019 Free Software Foundation, Inc.
+ Written by Markéta Calábková <mcalabkova@suse.cz>, 2019.
+
+ 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 3 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, see <https://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string.h>
+#include <time.h>
+
+#include "xalloc.h"
+#include "error.h"
+#include "xerror.h"
+#include "xvasprintf.h"
+
+#include "msgl-header.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include "gettext.h"
+
+#include "msgl-age.h"
+
+#define _(str) gettext (str)
+
+void
+msgdomain_sort_by_ages (msgdomain_list_ty **mdlps, size_t nfiles)
+{
+ qsort (mdlps, nfiles, sizeof(msgdomain_list_ty*), msgdomain_compare_ages);
+}
+
+int
+msgdomain_compare_ages (const void * p1, const void * p2)
+{
+ if (difftime((*(msgdomain_list_ty **)p2)->msgage, (*(msgdomain_list_ty **)p1)->msgage) > 0)
+ return 1;
+ else
+ return 0;
+}
+
+void
+msgdomain_read_ages (msgdomain_list_ty *mdlp)
+{
+ time_t times[mdlp->nitems];
+ const char * field = "PO-Revision-Date:";
+ char * where;
+
+ for (int i = 0; i < mdlp->nitems; i++)
+ {
+ message_list_ty *mlp = mdlp->item[i]->messages;
+
+ message_list_read_header_field (mlp, field, &where);
+ }
+ struct tm tmptm;
+ char *trail;
+ memset (&tmptm, 0, sizeof(struct tm));
+ if ((trail = strptime (where, "%Y-%m-%d %H:%M:%S%z", &tmptm)) != NULL)
+ mdlp->msgage = mktime (&tmptm);
+ else if ((trail = strptime (where, "%Y-%m-%d %H:%M:%S", &tmptm)) != NULL)
+ mdlp->msgage = mktime (&tmptm);
+ else if ((trail = strptime (where, "%Y-%m-%d %H:%M%z", &tmptm)) != NULL)
+ mdlp->msgage = mktime (&tmptm);
+ else if ((trail = strptime (where, "%Y-%m-%d %H:%M", &tmptm)) != NULL)
+ mdlp->msgage = mktime (&tmptm);
+ else
+ {
+ /* There is probably no creation date. Assign 0 and throw warning. */
+ mdlp->msgage = 0;
+ multiline_warning (xasprintf (_("warning: ")),
+ xasprintf (_("\
+PO-Revision-Date has no or invalid value, assuming it is old.\n\
+")));
+ return;
+ }
+ if ((*trail != '\n') && (*trail != '\0'))
+ multiline_warning (xasprintf (_("warning: ")),
+ xasprintf (_("\
+Unknown trailing characters after PO-Revision-Date, ignoring.\n\
+")));
+}
+
+
+
diff --git a/gettext-tools/src/msgl-age.h b/gettext-tools/src/msgl-age.h
new file mode 100644
index 0000000..af12dd9
--- /dev/null
+++ b/gettext-tools/src/msgl-age.h
@@ -0,0 +1,36 @@
+/* Message list header time simple parser.
+ Copyright (C) 2019 Free Software Foundation, Inc.
+ Written by Markéta Calábková <mcalabkova@suse.cz>, 2019.
+
+ 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 3 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, see <https://www.gnu.org/licenses/>. */
+
+#ifndef _MSGL_AGE_H
+#define _MSGL_AGE_H
+
+#include "message.h"
+
+/* Helper function to compare the ages, needed by qsort. */
+int
+ msgdomain_compare_ages (const void *, const void *);
+
+/* Simple function to modify intern ordering of files
+ * such that the newer file comes first. In fact it just calls qsort. */
+void
+ msgdomain_sort_by_ages (msgdomain_list_ty **, size_t);
+
+/* Parse dates in PO-Revision-Date: and store them inside the structure. */
+void
+ msgdomain_read_ages (msgdomain_list_ty *);
+
+#endif
diff --git a/gettext-tools/src/msgl-cat.c b/gettext-tools/src/msgl-cat.c
index b81f227..e338b6c 100644
--- a/gettext-tools/src/msgl-cat.c
+++ b/gettext-tools/src/msgl-cat.c
@@ -35,6 +35,7 @@
#include "message.h"
#include "read-catalog-file.h"
#include "po-charset.h"
+#include "msgl-age.h"
#include "msgl-ascii.h"
#include "msgl-ofn.h"
#include "msgl-equal.h"
@@ -57,6 +58,11 @@ int less_than;
If false, merge all available translations into one and fuzzy it. */
bool use_first;
+/* If true, sort all the translation according to their age and let
+ use_first finish the job.
+ If false, keep the order of translations. */
+bool use_newest;
+
/* If true, merge like msgcomm.
If false, merge like msgcat and msguniq. */
bool msgcomm_mode = false;
@@ -117,6 +123,15 @@ catenate_msgdomain_list (string_list_ty *file_list,
for (size_t n = 0; n < nfiles; n++)
mdlps[n] = read_catalog_file (files[n], input_syntax);
+ /* --use-newest case -- sort messages by time and then let --use-first finish the job */
+ if (use_newest)
+ {
+ for (size_t n = 0; n < nfiles; n++)
+ msgdomain_read_ages (mdlps[n]);
+
+ msgdomain_sort_by_ages (mdlps, nfiles);
+ }
+
/* Determine the canonical name of each input file's encoding. */
const char ***canon_charsets = XNMALLOC (nfiles, const char **);
for (size_t n = 0; n < nfiles; n++)
diff --git a/gettext-tools/src/msgl-cat.h b/gettext-tools/src/msgl-cat.h
index d4b1d19..f43a0c2 100644
--- a/gettext-tools/src/msgl-cat.h
+++ b/gettext-tools/src/msgl-cat.h
@@ -38,6 +38,7 @@ extern LIBGETTEXTSRC_DLL_VARIABLE int less_than;
/* If true, use the first available translation.
If false, merge all available translations into one and fuzzy it. */
extern LIBGETTEXTSRC_DLL_VARIABLE bool use_first;
+extern LIBGETTEXTSRC_DLL_VARIABLE bool use_newest;
/* If true, merge like msgcomm.
If false, merge like msgcat and msguniq. */
diff --git a/gettext-tools/src/msgl-header.c b/gettext-tools/src/msgl-header.c
index 304ef2c..352c537 100644
--- a/gettext-tools/src/msgl-header.c
+++ b/gettext-tools/src/msgl-header.c
@@ -238,3 +238,49 @@ message_list_delete_header_field (message_list_ty *mlp,
}
}
}
+
+void
+message_list_read_header_field (message_list_ty *mlp,
+ const char *field, char **where_ptr)
+{
+ size_t field_len = strlen (field);
+ size_t j;
+
+ /* Search the header entry. */
+ for (j = 0; j < mlp->nitems; j++)
+ if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
+ {
+ /* We found the correct message. */
+ message_ty *mp = mlp->item[j];
+
+ /* Tag the header entry. */
+ const char *header = mp->msgstr;
+
+ /* Test whether the field occurs in the header entry. */
+ const char *h;
+
+ for (h = header; *h != '\0'; )
+ {
+ if (strncmp (h, field, field_len) == 0)
+ break;
+ /* Jump by lines. */
+ h = strchr (h, '\n');
+ if (h == NULL)
+ break;
+ h++;
+ }
+ if (h != NULL && *h != '\0')
+ {
+ /* We found it and it is nonempty. Read the value of the field. */
+ h += field_len + 1;
+ char *enh = strchr (h, '\n');
+ if (enh != NULL && *enh != '\0')
+ {
+ *where_ptr = (char *)XNMALLOC (((enh - h) + 1), char);
+ memcpy (*where_ptr, h, enh - h);
+ /* Make the string null-terminated. */
+ (*where_ptr)[enh-h] = '\0';
+ }
+ }
+ }
+}
diff --git a/gettext-tools/src/msgl-header.h b/gettext-tools/src/msgl-header.h
index a98db95..933295b 100644
--- a/gettext-tools/src/msgl-header.h
+++ b/gettext-tools/src/msgl-header.h
@@ -49,6 +49,12 @@ extern void
message_list_delete_header_field (message_list_ty *mlp,
const char *field);
+/* Read the given field from the header.
+ The FIELD name ends in a colon. */
+extern void
+ message_list_read_header_field (message_list_ty *mlp,
+ const char *field, char **where_ptr);
+
#ifdef __cplusplus
}
--
2.52.0