* gettext-tools/src/if-error.h: Include <stdarg.h>.
(if_verror): New declaration.
* gettext-tools/src/if-error.c (if_verror): New function, extracted from
if_error.
(if_error): Invoke it.
* gettext-tools/src/xg-check.h (xgettext_check_message_list): Renamed from
syntax_check_message_list.
* gettext-tools/src/xg-check.c: Include format.h, if-error.h.
(struct formatstring_error_logger_locals): New type.
(formatstring_error_logger, format_check_message): New functions.
(xgettext_check_message_list): Renamed from syntax_check_message_list. Invoke
also format_check_message.
* gettext-tools/src/xgettext.c (main): Invoke xgettext_check_message_list
instead of syntax_check_message_list.
* gettext-tools/tests/xgettext-19: New file.
* gettext-tools/tests/Makefile.am (TESTS): Add it.
* NEWS: Mention the change.
- xgettext now recognizes the \c, \u, and \U escape sequences in dollar-
single-quoted strings $'...'.
+# Improvements for maintainers:
+ * xgettext:
+ - When extracting a message with plural that is some format string,
+ xgettext now verifies that the msgid and msgid_plural are compatible
+ as format strings. For most format string types, this still allows
+ omitting from msgid a placeholder that is used in msgid_plural. But
+ when a placeholder is used in both msgid and msgid_plural, its type
+ must be the same in both.
+
# Improvements for translators:
* msggrep:
- msggrep accepts two new options -W/--workflow-flags and -S/--sticky-flags
/* Error handling during reading of input files.
- Copyright (C) 2023-2024 Free Software Foundation, Inc.
+ Copyright (C) 2023-2025 Free Software Foundation, Inc.
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
void
-if_error (int severity,
- const char *filename, size_t lineno, size_t column,
- bool multiline, const char *format, ...)
+if_verror (int severity,
+ const char *filename, size_t lineno, size_t column,
+ bool multiline, const char *format, va_list args)
{
const char *prefix_tail =
(severity == IF_SEVERITY_WARNING ? _("warning: ") : _("error: "));
- va_list args;
- va_start (args, format);
char *message_text = xvasprintf (format, args);
if (message_text == NULL)
message_text = xstrdup (severity == IF_SEVERITY_WARNING
}
error_with_progname = true;
- va_end (args);
if (severity == IF_SEVERITY_FATAL_ERROR)
exit (EXIT_FAILURE);
}
+
+void
+if_error (int severity,
+ const char *filename, size_t lineno, size_t column,
+ bool multiline, const char *format, ...)
+{
+ va_list args;
+
+ va_start (args, format);
+ if_verror (severity, filename, lineno, column, multiline, format, args);
+ va_end (args);
+}
/* Error handling during reading of input files.
- Copyright (C) 2023 Free Software Foundation, Inc.
+ Copyright (C) 2023-2025 Free Software Foundation, Inc.
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
#ifndef _IF_ERROR_H
#define _IF_ERROR_H
+#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
__attribute__ ((__format__ (__printf__, 6, 7)))
#endif
;
+extern void if_verror (int severity,
+ const char *filename, size_t lineno, size_t column,
+ bool multiline, const char *format, va_list args)
+#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)
+ __attribute__ ((__format__ (__printf__, 6, 0)))
+#endif
+ ;
#ifdef __cplusplus
/* Checking of messages in POT files: so-called "syntax checks".
- Copyright (C) 2015-2023 Free Software Foundation, Inc.
+ Copyright (C) 2015-2025 Free Software Foundation, Inc.
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
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
-/* Written by Daiki Ueno <ueno@gnu.org>. */
+/* Written by Daiki Ueno <ueno@gnu.org> and Bruno Haible <bruno@clisp.org>. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#include "xalloc.h"
#include "xvasprintf.h"
#include "message.h"
+#include "format.h"
#include "po-xerror.h"
+#include "if-error.h"
#include "sentence.h"
#include "c-ctype.h"
#include "unictype.h"
}
-/* Perform all syntax checks on a message list.
+/* Signal an error when checking format strings. */
+struct formatstring_error_logger_locals
+{
+ const lex_pos_ty *pos;
+};
+static void
+formatstring_error_logger (void *data, const char *format, ...)
+#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ > 2)
+ __attribute__ ((__format__ (__printf__, 2, 3)))
+#endif
+;
+static void
+formatstring_error_logger (void *data, const char *format, ...)
+{
+ struct formatstring_error_logger_locals *l =
+ (struct formatstring_error_logger_locals *) data;
+ va_list args;
+
+ va_start (args, format);
+ if_verror (IF_SEVERITY_ERROR,
+ l->pos->file_name, l->pos->line_number, (size_t)(-1), false,
+ format, args);
+ va_end (args);
+}
+
+
+/* Perform all format checks on a non-obsolete message.
+ Return the number of errors that were seen. */
+static int
+format_check_message (const message_ty *mp)
+{
+ int seen_errors = 0;
+ size_t i;
+
+ if (mp->msgid_plural != NULL)
+ {
+ /* Look for format string incompatibilities between msgid and
+ msgid_plural. */
+ for (i = 0; i < NFORMATS; i++)
+ if (possible_format_p (mp->is_format[i]))
+ {
+ struct formatstring_parser *parser = formatstring_parsers[i];
+ char *invalid_reason1 = NULL;
+ void *descr1 =
+ parser->parse (mp->msgid, false, NULL, &invalid_reason1);
+ char *invalid_reason2 = NULL;
+ void *descr2 =
+ parser->parse (mp->msgid_plural, false, NULL, &invalid_reason2);
+
+ if (descr1 != NULL && descr2 != NULL)
+ {
+ struct formatstring_error_logger_locals locals;
+ locals.pos = &mp->pos;
+ if (parser->check (descr2, descr1, false,
+ formatstring_error_logger, &locals,
+ "msgid_plural", "msgid"))
+ seen_errors++;
+ }
+
+ if (descr2 != NULL)
+ parser->free (descr2);
+ else
+ free (invalid_reason2);
+ if (descr1 != NULL)
+ parser->free (descr1);
+ else
+ free (invalid_reason1);
+ }
+ }
+
+ return seen_errors;
+}
+
+
+/* Perform all checks on a message list.
Return the number of errors that were seen. */
int
-syntax_check_message_list (message_list_ty *mlp)
+xgettext_check_message_list (message_list_ty *mlp)
{
int seen_errors = 0;
size_t j;
message_ty *mp = mlp->item[j];
if (!is_header (mp))
- seen_errors += syntax_check_message (mp);
+ seen_errors += syntax_check_message (mp) + format_check_message (mp);
}
return seen_errors;
/* Checking of messages in POT files: so-called "syntax checks".
- Copyright (C) 2015-2023 Free Software Foundation, Inc.
+ Copyright (C) 2015-2025 Free Software Foundation, Inc.
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
#endif
-/* Perform all syntax checks on a message list.
+/* Perform all checks on a message list.
Return the number of errors that were seen. */
-extern int syntax_check_message_list (message_list_ty *mlp);
+extern int xgettext_check_message_list (message_list_ty *mlp);
#ifdef __cplusplus
else if (sort_by_msgid)
msgdomain_list_sort_by_msgid (mdlp);
- /* Check syntax of messages. */
+ /* Check msgid and msgid_plural of messages. */
{
int nerrors = 0;
for (i = 0; i < mdlp->nitems; i++)
{
message_list_ty *mlp = mdlp->item[i]->messages;
- nerrors += syntax_check_message_list (mlp);
+ nerrors += xgettext_check_message_list (mlp);
}
/* Exit with status 1 on any error. */
xgettext-2 xgettext-3 xgettext-4 xgettext-5 xgettext-6 \
xgettext-7 xgettext-8 xgettext-9 xgettext-10 xgettext-11 xgettext-12 \
xgettext-13 xgettext-14 xgettext-15 xgettext-16 xgettext-17 \
- xgettext-18 \
+ xgettext-18 xgettext-19 \
xgettext-combine-1 xgettext-combine-2 xgettext-combine-3 \
xgettext-git-1 \
xgettext-appdata-1 xgettext-appdata-2 xgettext-appdata-3 \
--- /dev/null
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test that msgid and msgid_plural are checked to be compatible as
+# format strings.
+# <https://savannah.gnu.org/bugs/?63630>
+
+cat <<\EOF > xg-test19.c
+ if (filedata->is_separate)
+ printf (ngettext ("\nIn linked file '%s' the dynamic section at offset %#" PRIx64 " contains %" PRIu64 " entry:\n",
+ "\nIn linked file '%s' the dynamic section at offset %#" PRIx64 " contains %" PRIu64 " entries:\n",
+ filedata->dynamic_nent),
+ filedata->file_name,
+ filedata->dynamic_addr,
+ filedata->dynamic_nent);
+ else
+ printf (ngettext ("\nDynamic section at offset %#" PRIx64 " contains %" PRId64 " entry:\n",
+ "\nDynamic section at offset %#" PRIx64 " contains %" PRIu64 " entries:\n",
+ filedata->dynamic_nent),
+ filedata->dynamic_addr,
+ filedata->dynamic_nent);
+EOF
+
+: ${XGETTEXT=xgettext}
+LANGUAGE= LC_ALL=C ${XGETTEXT} --omit-header --add-comments -d xg-test19.tmp xg-test19.c 2>xg-test19.err
+test $? = 1 || Exit 1
+grep "format specifications in 'msgid_plural' and 'msgid' for argument 2 are not the same" xg-test19.err || Exit 1