From: Bruno Haible Date: Sat, 10 Jan 2026 19:16:46 +0000 (+0100) Subject: xgettext: Allow inhibiting the warnings about URLs and email addresses. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f1459a89e25553457aacdbd7c2ef1a7ceb3c8be2;p=thirdparty%2Fgettext.git xgettext: Allow inhibiting the warnings about URLs and email addresses. Reported at . * gettext-tools/src/message.h (enum syntax_check_type): Add sc_url, sc_email. (NSYNTAXCHECKS): Increase by 2. * gettext-tools/src/message.c (syntax_check_name): Update. * gettext-tools/src/xgettext.h (default_syntax_check): Add comment. * gettext-tools/src/xgettext.c (default_syntax_check): Add initializer. (main): Recognize --no-check option. (usage): Document --no-check option. * gettext-tools/src/xg-message.c (decide_syntax_check): Assume that default_syntax_check[i] != undecided. * gettext-tools/src/xg-check.c (syntax_check_function): Remove the second argument. (string_has_ascii_ellipsis): New function, extracted from syntax_check_ellipsis_unicode. (message_has_ascii_ellipsis): New function. (syntax_check_ellipsis_unicode): Remove the second argument. Simplify. Emit only a single error for both msgid and msgid_plural. (string_has_space_ellipsis): New function, extracted from syntax_check_space_ellipsis. (message_has_space_ellipsis): New function. (syntax_check_space_ellipsis): Remove the second argument. Simplify. Emit only a single error for both msgid and msgid_plural. (syntax_check_quote_unicode): Remove the second argument. (syntax_check_bullet_unicode_string): New function, extracted from syntax_check_bullet_unicode. (syntax_check_bullet_unicode): Remove the second argument. Simplify. (string_has_url): Don't recognize 'mailto:' URLs. (syntax_check_url, syntax_check_email): New functions, extracted from url_check_message. (url_check_message): Remove function. (sc_funcs): Add syntax_check_url, syntax_check_email. (syntax_check_message): Simplify. (xgettext_check_message_list): Don't invoke url_check_message. * gettext-tools/tests/xgettext-14: Update after xg-check.c changesd. * gettext-tools/tests/xgettext-20: Add more testcases. * gettext-tools/doc/xgettext.texi: Document the --no-check option. * gettext-tools/doc/gettext.texi: Bump copyright year. * NEWS: Mention the change. --- diff --git a/NEWS b/NEWS index 43543a287..e08116b02 100644 --- a/NEWS +++ b/NEWS @@ -43,6 +43,16 @@ Version 1.0 - December 2025 message. - The documentation has a new chapter "Pretranslation". +# Improvements for maintainers: + * xgettext: + - The refactoring suggestion when a translatable string contains an URL + or email address can now be inhibited through a command-line option + '--no-check=url' or '--no-check=email', or through a comment in the + source code of the form + /* xgettext: no-url-check */ + or + /* xgettext: no-email-check */ + # Programming languages support: * OCaml: - xgettext now supports OCaml. diff --git a/gettext-tools/doc/gettext.texi b/gettext-tools/doc/gettext.texi index f2f095861..2929ef5db 100644 --- a/gettext-tools/doc/gettext.texi +++ b/gettext-tools/doc/gettext.texi @@ -94,7 +94,7 @@ This file provides documentation for GNU @code{gettext} utilities. It also serves as a reference for the free Translation Project. @copying -Copyright (C) 1995-1998, 2001-2025 Free Software Foundation, Inc. +Copyright (C) 1995-1998, 2001-2026 Free Software Foundation, Inc. This manual is free documentation. It is dually licensed under the GNU FDL and the GNU GPL. This means that you can redistribute this @@ -129,7 +129,7 @@ A copy of the license is included in @ref{GNU GPL}. @page @vskip 0pt plus 1filll @c @insertcopying -Copyright (C) 1995-1998, 2001-2025 Free Software Foundation, Inc. +Copyright (C) 1995-1998, 2001-2026 Free Software Foundation, Inc. This manual is free documentation. It is dually licensed under the GNU FDL and the GNU GPL. This means that you can redistribute this diff --git a/gettext-tools/doc/xgettext.texi b/gettext-tools/doc/xgettext.texi index b089aa629..e2f32c5b4 100644 --- a/gettext-tools/doc/xgettext.texi +++ b/gettext-tools/doc/xgettext.texi @@ -1,5 +1,5 @@ @c This file is part of the GNU gettext manual. -@c Copyright (C) 1995-2025 Free Software Foundation, Inc. +@c Copyright (C) 1995-2026 Free Software Foundation, Inc. @c See the file gettext.texi for copying conditions. @pindex xgettext @@ -188,8 +188,8 @@ the line with the string there is merely a blank line. @item --check[=@var{CHECK}] @opindex --check@r{, @code{xgettext} option} @cindex supported syntax checks, @code{xgettext} -Perform a syntax check on msgid and msgid_plural. The supported checks -are: +Perform a syntax check on msgid and msgid_plural. +The supported checks that are disabled by default and that can be enabled are: @table @samp @item ellipsis-unicode @@ -223,11 +223,37 @@ where @var{name} is the name of a valid syntax check. If a flag is prefixed by @code{no-}, the meaning is negated. Some tests apply the checks to each sentence within the msgid, rather -than the whole string. xgettext detects the end of sentence by +than the whole string. @code{xgettext} detects the end of sentence by performing a pattern match, which usually looks for a period followed by a certain number of spaces. The number is specified with the @code{--sentence-end} option. +@item --no-check[=@var{CHECK}] +@opindex --no-check@r{, @code{xgettext} option} +Don't perform a syntax check on msgid and msgid_plural +that is enabled by default. +The supported checks that are enabled by default are: + +@table @samp +@item url +Prohibit a URL inside a string. + +@item email +Prohibit an email address inside a string. + +@end table + +The option has an effect on all input files. +To disable a check for a certain string, +you can mark it with an @code{xgettext:} special comment in the source file. +For example, if you want to suppress the check on a particular string, +add the following comment: + +@example +/* xgettext: no-email-check */ +gettext ("Specify your@@email-address.com here."); +@end example + @item --sentence-end[=@var{TYPE}] @opindex --sentence-end@r{, @code{xgettext} option} @cindex sentence end markers, @code{xgettext} diff --git a/gettext-tools/src/message.c b/gettext-tools/src/message.c index 48c4b6b9c..f3127f47d 100644 --- a/gettext-tools/src/message.c +++ b/gettext-tools/src/message.c @@ -1,5 +1,5 @@ /* GNU gettext - internationalization aids - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file was written by Peter Miller @@ -175,7 +175,9 @@ const char *const syntax_check_name[NSYNTAXCHECKS] = /* sc_ellipsis_unicode */ "ellipsis-unicode", /* sc_space_ellipsis */ "space-ellipsis", /* sc_quote_unicode */ "quote-unicode", - /* sc_bullet_unicode */ "bullet-unicode" + /* sc_bullet_unicode */ "bullet-unicode", + /* sc_url */ "url", + /* sc_email */ "email" }; diff --git a/gettext-tools/src/message.h b/gettext-tools/src/message.h index b37e4890c..52a5ce4be 100644 --- a/gettext-tools/src/message.h +++ b/gettext-tools/src/message.h @@ -1,5 +1,5 @@ /* GNU gettext - internationalization aids - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file was written by Peter Miller @@ -134,9 +134,11 @@ enum syntax_check_type sc_ellipsis_unicode, sc_space_ellipsis, sc_quote_unicode, - sc_bullet_unicode + sc_bullet_unicode, + sc_url, + sc_email }; -#define NSYNTAXCHECKS 4 +#define NSYNTAXCHECKS 6 extern LIBGETTEXTSRC_DLL_VARIABLE const char *const syntax_check_name[NSYNTAXCHECKS]; /* Is current msgid subject to a syntax check? */ diff --git a/gettext-tools/src/xg-check.c b/gettext-tools/src/xg-check.c index b9e53b4a1..7c41f1576 100644 --- a/gettext-tools/src/xg-check.c +++ b/gettext-tools/src/xg-check.c @@ -1,5 +1,5 @@ /* Checking of messages in POT files: so-called "syntax checks". - Copyright (C) 2015-2025 Free Software Foundation, Inc. + Copyright (C) 2015-2026 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 @@ -45,117 +45,148 @@ /* Function that implements a single syntax check. MP is a message. - MSGID is either MP->msgid or MP->msgid_plural. Returns the number of errors that were seen and reported. */ -typedef int (* syntax_check_function) (const message_ty *mp, const char *msgid); +typedef int (* syntax_check_function) (const message_ty *mp); -/* Implementation of the sc_ellipsis_unicode syntax check. */ +/* ----- Implementation of the sc_ellipsis_unicode syntax check. ----- */ -static int -syntax_check_ellipsis_unicode (const message_ty *mp, const char *msgid) +/* Determine whether a string (msgid or msgid_plural) contains an ASCII + ellipsis. */ +static bool +string_has_ascii_ellipsis (const char *string) { - int seen_errors = 0; - { - const char *str = msgid; - const char *str_limit = str + strlen (msgid); - while (str < str_limit) - { - ucs4_t ending_char; - const char *end = sentence_end (str, &ending_char); + const char *str = string; + const char *str_limit = str + strlen (string); + while (str < str_limit) + { + ucs4_t ending_char; + const char *end = sentence_end (str, &ending_char); - /* sentence_end doesn't treat '...' specially. */ - const char *cp = end - (ending_char == '.' ? 2 : 3); + /* sentence_end doesn't treat '...' specially. */ + const char *cp = end - (ending_char == '.' ? 2 : 3); - if (cp >= str && memcmp (cp, "...", 3) == 0) - { - po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false, - _("ASCII ellipsis ('...') instead of Unicode")); - seen_errors++; - } + if (cp >= str && memcmp (cp, "...", 3) == 0) + return true; - str = end + 1; - } - } + str = end + 1; + } + return false; +} - return seen_errors; +/* Determine whether a message contains an ASCII ellipsis. */ +static bool +message_has_ascii_ellipsis (const message_ty *mp) +{ + return string_has_ascii_ellipsis (mp->msgid) + || (mp->msgid_plural != NULL + && string_has_ascii_ellipsis (mp->msgid_plural)); } +static int +syntax_check_ellipsis_unicode (const message_ty *mp) +{ + if (message_has_ascii_ellipsis (mp)) + { + po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false, + _("ASCII ellipsis ('...') instead of Unicode")); + return 1; + } -/* Implementation of the sc_space_ellipsis syntax check. */ + return 0; +} -static int -syntax_check_space_ellipsis (const message_ty *mp, const char *msgid) + +/* ----- Implementation of the sc_space_ellipsis syntax check. ----- */ + +/* Determine whether a string (msgid or msgid_plural) contains a space before + an ellipsis. */ +static bool +string_has_space_ellipsis (const char *string) { - int seen_errors = 0; - { - const char *str = msgid; - const char *str_limit = str + strlen (msgid); - while (str < str_limit) - { - ucs4_t ending_char; - const char *end = sentence_end (str, &ending_char); + const char *str = string; + const char *str_limit = str + strlen (string); + while (str < str_limit) + { + ucs4_t ending_char; + const char *end = sentence_end (str, &ending_char); - const char *ellipsis = NULL; - if (ending_char == 0x2026) - ellipsis = end; - else if (ending_char == '.') - { - /* sentence_end doesn't treat '...' specially. */ - const char *cp = end - 2; - if (cp >= str && memcmp (cp, "...", 3) == 0) - ellipsis = cp; - } - else - { - /* Look for a '...'. */ - const char *cp = end - 3; - if (cp >= str && memcmp (cp, "...", 3) == 0) - ellipsis = cp; - else - { - /* Look for a U+2026. */ - ucs4_t uc = 0xfffd; - for (cp = end - 1; cp >= str; cp--) - { - u8_mbtouc (&uc, (const unsigned char *) cp, end - cp); - if (uc != 0xfffd) - break; - } + const char *ellipsis = NULL; + if (ending_char == 0x2026) + ellipsis = end; + else if (ending_char == '.') + { + /* sentence_end doesn't treat '...' specially. */ + const char *cp = end - 2; + if (cp >= str && memcmp (cp, "...", 3) == 0) + ellipsis = cp; + } + else + { + /* Look for a '...'. */ + const char *cp = end - 3; + if (cp >= str && memcmp (cp, "...", 3) == 0) + ellipsis = cp; + else + { + /* Look for a U+2026. */ + ucs4_t uc = 0xfffd; + for (cp = end - 1; cp >= str; cp--) + { + u8_mbtouc (&uc, (const unsigned char *) cp, end - cp); + if (uc != 0xfffd) + break; + } - if (uc == 0x2026) - ellipsis = cp; - } - } + if (uc == 0x2026) + ellipsis = cp; + } + } - if (ellipsis) - { - /* Look at the character before ellipsis. */ - ucs4_t uc = 0xfffd; - for (const char *cp = ellipsis - 1; cp >= str; cp--) - { - u8_mbtouc (&uc, (const unsigned char *) cp, ellipsis - cp); - if (uc != 0xfffd) - break; - } + if (ellipsis) + { + /* Look at the character before ellipsis. */ + ucs4_t uc = 0xfffd; + for (const char *cp = ellipsis - 1; cp >= str; cp--) + { + u8_mbtouc (&uc, (const unsigned char *) cp, ellipsis - cp); + if (uc != 0xfffd) + break; + } - if (uc != 0xfffd && uc_is_space (uc)) - { - po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false, - _("space before ellipsis found in user visible strings")); - seen_errors++; - } - } + if (uc != 0xfffd && uc_is_space (uc)) + return true; + } - str = end + 1; - } - } + str = end + 1; + } + return false; +} - return seen_errors; +/* Determine whether a message contains a space before an ellipsis. */ +static bool +message_has_space_ellipsis (const message_ty *mp) +{ + return string_has_space_ellipsis (mp->msgid) + || (mp->msgid_plural != NULL + && string_has_space_ellipsis (mp->msgid_plural)); } +static int +syntax_check_space_ellipsis (const message_ty *mp) +{ + if (message_has_space_ellipsis (mp)) + { + po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false, + _("space before ellipsis found in user visible string")); + return 1; + } + + return 0; +} -/* Implementation of the sc_quote_unicode syntax check. */ + +/* ----- Implementation of the sc_quote_unicode syntax check. ----- */ struct callback_arg { @@ -189,20 +220,23 @@ syntax_check_quote_unicode_callback (char quote, const char *quoted, } static int -syntax_check_quote_unicode (const message_ty *mp, const char *msgid) +syntax_check_quote_unicode (const message_ty *mp) { struct callback_arg arg; arg.mp = mp; arg.seen_errors = 0; - scan_quoted (msgid, strlen (msgid), + scan_quoted (mp->msgid, strlen (mp->msgid), syntax_check_quote_unicode_callback, &arg); + if (mp->msgid_plural != NULL) + scan_quoted (mp->msgid_plural, strlen (mp->msgid_plural), + syntax_check_quote_unicode_callback, &arg); return arg.seen_errors; } -/* Implementation of the sc_bullet_unicode syntax check. */ +/* ----- Implementation of the sc_bullet_unicode syntax check. ----- */ struct bullet_ty { @@ -220,7 +254,7 @@ struct bullet_stack_ty static struct bullet_stack_ty bullet_stack; static int -syntax_check_bullet_unicode (const message_ty *mp, const char *msgid) +syntax_check_bullet_unicode_string (const message_ty *mp, const char *msgid) { bool seen_error = false; @@ -306,110 +340,18 @@ syntax_check_bullet_unicode (const message_ty *mp, const char *msgid) return 0; } - -/* List of all syntax checks. */ -static const syntax_check_function sc_funcs[NSYNTAXCHECKS] = -{ - syntax_check_ellipsis_unicode, - syntax_check_space_ellipsis, - syntax_check_quote_unicode, - syntax_check_bullet_unicode -}; - - -/* Perform all syntax checks on a non-obsolete message. - Return the number of errors that were seen. */ static int -syntax_check_message (const message_ty *mp) +syntax_check_bullet_unicode (const message_ty *mp) { - int seen_errors = 0; - - for (int i = 0; i < NSYNTAXCHECKS; i++) - { - if (mp->do_syntax_check[i] == yes) - { - seen_errors += sc_funcs[i] (mp, mp->msgid); - if (mp->msgid_plural) - seen_errors += sc_funcs[i] (mp, mp->msgid_plural); - } - } - - return seen_errors; + return syntax_check_bullet_unicode_string (mp, mp->msgid) + + (mp->msgid_plural != NULL + ? syntax_check_bullet_unicode_string (mp, mp->msgid_plural) + : 0); } -/* 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; - - if (mp->msgid_plural != NULL) - { - /* Look for format string incompatibilities between msgid and - msgid_plural. */ - for (size_t 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; -} - +/* ----- Implementation of the sc_url syntax check. ----- */ +/* This check is enabled by default. It produces a warning, not an error. */ /* Determine whether a string (msgid or msgid_plural) contains a URL. */ static bool @@ -419,7 +361,12 @@ string_has_url (const char *string) (not "file:"). */ static const char *patterns[] = { + /* We can afford to be silent about 'mailto:' here, because it is + almost always followed by an email address, that we report though + the sc_email check. */ + #if 0 "mailto:", + #endif "http://", "https://", "ftp://", "irc://", "ircs://" @@ -473,6 +420,20 @@ message_has_url (const message_ty *mp) || (mp->msgid_plural != NULL && string_has_url (mp->msgid_plural)); } +static int +syntax_check_url (const message_ty *mp) +{ + if (message_has_url (mp)) + if_error (IF_SEVERITY_WARNING, + mp->pos.file_name, mp->pos.line_number, (size_t)(-1), false, + _("Message contains an embedded URL. Better move it out of the translatable string, see %s"), + "https://www.gnu.org/software/gettext/manual/html_node/No-embedded-URLs.html"); + return 0; +} + + +/* ----- Implementation of the sc_email syntax check. ----- */ +/* This check is enabled by default. It produces a warning, not an error. */ /* Determine whether a string (msgid or msgid_plural) contains an email address. */ @@ -551,21 +512,116 @@ message_has_email (const message_ty *mp) || (mp->msgid_plural != NULL && string_has_email (mp->msgid_plural)); } - -/* Perform the URL check on a non-obsolete message. */ -static void -url_check_message (const message_ty *mp) +static int +syntax_check_email (const message_ty *mp) { - if (message_has_url (mp)) - if_error (IF_SEVERITY_WARNING, - mp->pos.file_name, mp->pos.line_number, (size_t)(-1), false, - _("Message contains an embedded URL. Better move it out of the translatable string, see %s"), - "https://www.gnu.org/software/gettext/manual/html_node/No-embedded-URLs.html"); - else if (message_has_email (mp)) + if (message_has_email (mp)) if_error (IF_SEVERITY_WARNING, mp->pos.file_name, mp->pos.line_number, (size_t)(-1), false, _("Message contains an embedded email address. Better move it out of the translatable string, see %s"), "https://www.gnu.org/software/gettext/manual/html_node/No-embedded-URLs.html"); + + return 0; +} + + +/* ---------------------- List of all syntax checks. ---------------------- */ +static const syntax_check_function sc_funcs[NSYNTAXCHECKS] = +{ + syntax_check_ellipsis_unicode, + syntax_check_space_ellipsis, + syntax_check_quote_unicode, + syntax_check_bullet_unicode, + syntax_check_url, + syntax_check_email +}; + + +/* Perform all syntax checks on a non-obsolete message. + Return the number of errors that were seen. */ +static int +syntax_check_message (const message_ty *mp) +{ + int seen_errors = 0; + + for (int i = 0; i < NSYNTAXCHECKS; i++) + if (mp->do_syntax_check[i] == yes) + seen_errors += sc_funcs[i] (mp); + + return seen_errors; +} + + +/* 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; + + if (mp->msgid_plural != NULL) + { + /* Look for format string incompatibilities between msgid and + msgid_plural. */ + for (size_t 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; } @@ -583,7 +639,6 @@ xgettext_check_message_list (message_list_ty *mlp) if (!is_header (mp)) { seen_errors += syntax_check_message (mp) + format_check_message (mp); - url_check_message (mp); } } diff --git a/gettext-tools/src/xg-message.c b/gettext-tools/src/xg-message.c index 685713adf..d649f20b1 100644 --- a/gettext-tools/src/xg-message.c +++ b/gettext-tools/src/xg-message.c @@ -1,5 +1,5 @@ /* Extracting a message. Accumulating the message list. - Copyright (C) 2001-2025 Free Software Foundation, Inc. + Copyright (C) 2001-2026 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 @@ -231,7 +231,7 @@ decide_syntax_check (message_ty *mp) { for (size_t i = 0; i < NSYNTAXCHECKS; i++) if (mp->do_syntax_check[i] == undecided) - mp->do_syntax_check[i] = default_syntax_check[i] == yes ? yes : no; + mp->do_syntax_check[i] = default_syntax_check[i]; } diff --git a/gettext-tools/src/xgettext.c b/gettext-tools/src/xgettext.c index b57519f61..295932b16 100644 --- a/gettext-tools/src/xgettext.c +++ b/gettext-tools/src/xgettext.c @@ -1,5 +1,5 @@ /* Extracts strings from C source file to Uniforum style .po file. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. Written by Ulrich Drepper , April 1995. This program is free software: you can redistribute it and/or modify @@ -239,8 +239,16 @@ static bool recognize_format_kde; /* If true, recognize Boost format strings. */ static bool recognize_format_boost; -/* Syntax checks enabled by default. */ -enum is_syntax_check default_syntax_check[NSYNTAXCHECKS]; +/* Syntax checks enabled through a command-line option or by default. */ +enum is_syntax_check default_syntax_check[NSYNTAXCHECKS] = +{ + /* sc_ellipsis_unicode */ no, + /* sc_space_ellipsis */ no, + /* sc_quote_unicode */ no, + /* sc_bullet_unicode */ no, + /* sc_url */ yes, + /* sc_email */ yes +}; static locating_rule_list_ty *its_locating_rules; @@ -391,11 +399,11 @@ main (int argc, char *argv[]) { "force-po", 0, no_argument, &force_po, 1 }, { "foreign-user", CHAR_MAX + 2, no_argument }, { "from-code", CHAR_MAX + 3, required_argument }, - { "generated", CHAR_MAX + 24, required_argument }, + { "generated", CHAR_MAX + 25, required_argument }, { "help", 'h', no_argument }, { "indent", 'i', no_argument }, - { "its", CHAR_MAX + 20, required_argument }, - { "itstool", CHAR_MAX + 19, no_argument }, + { "its", CHAR_MAX + 21, required_argument }, + { "itstool", CHAR_MAX + 20, no_argument }, { "join-existing", 'j', no_argument }, { "kde", CHAR_MAX + 10, no_argument }, { "keyword", 'k', optional_argument }, @@ -403,8 +411,9 @@ main (int argc, char *argv[]) { "msgid-bugs-address", CHAR_MAX + 5, required_argument }, { "msgstr-prefix", 'm', optional_argument }, { "msgstr-suffix", 'M', optional_argument }, + { "no-check", CHAR_MAX + 18, required_argument }, { "no-escape", 'e', no_argument }, - { "no-git", CHAR_MAX + 23, no_argument }, + { "no-git", CHAR_MAX + 24, no_argument }, { "no-location", CHAR_MAX + 16, no_argument }, { "no-wrap", CHAR_MAX + 4, no_argument }, { "omit-header", 0, no_argument, &xgettext_omit_header, 1 }, @@ -414,15 +423,15 @@ main (int argc, char *argv[]) { "package-version", CHAR_MAX + 13, required_argument }, { "properties-output", CHAR_MAX + 6, no_argument }, { "qt", CHAR_MAX + 9, no_argument }, - { "reference", CHAR_MAX + 22, required_argument }, - { "sentence-end", CHAR_MAX + 18, required_argument }, + { "reference", CHAR_MAX + 23, required_argument }, + { "sentence-end", CHAR_MAX + 19, required_argument }, { "sort-by-file", 'F', no_argument }, { "sort-output", 's', no_argument }, - { "strict", CHAR_MAX + 25, no_argument }, + { "strict", CHAR_MAX + 26, no_argument }, { "string-limit", 'l', required_argument }, { "stringtable-output", CHAR_MAX + 7, no_argument }, { "style", CHAR_MAX + 15, required_argument }, - { "tag", CHAR_MAX + 21, required_argument }, + { "tag", CHAR_MAX + 22, required_argument }, { "trigraphs", 'T', no_argument }, { "verbose", 'v', no_argument }, { "version", 'V', no_argument }, @@ -604,7 +613,7 @@ main (int argc, char *argv[]) sort_by_msgid = true; break; - case CHAR_MAX + 25: /* --strict */ + case CHAR_MAX + 26: /* --strict */ message_print_style_uniforum (); break; @@ -722,7 +731,23 @@ main (int argc, char *argv[]) } break; - case CHAR_MAX + 18: /* --sentence-end */ + case CHAR_MAX + 18: /* --no-check */ + { + size_t i; + for (i = 0; i < NSYNTAXCHECKS; i++) + { + if (strcmp (optarg, syntax_check_name[i]) == 0) + { + default_syntax_check[i] = no; + break; + } + } + if (i == NSYNTAXCHECKS) + error (EXIT_FAILURE, 0, _("syntax check '%s' unknown"), optarg); + } + break; + + case CHAR_MAX + 19: /* --sentence-end */ if (strcmp (optarg, "single-space") == 0) sentence_end_required_spaces = 1; else if (strcmp (optarg, "double-space") == 0) @@ -731,27 +756,27 @@ main (int argc, char *argv[]) error (EXIT_FAILURE, 0, _("sentence end type '%s' unknown"), optarg); break; - case CHAR_MAX + 19: /* --itstool */ + case CHAR_MAX + 20: /* --itstool */ add_itstool_comments = true; break; - case CHAR_MAX + 20: /* --its */ + case CHAR_MAX + 21: /* --its */ explicit_its_filename = optarg; break; - case CHAR_MAX + 21: /* --tag */ + case CHAR_MAX + 22: /* --tag */ x_javascript_tag (optarg); break; - case CHAR_MAX + 22: /* --reference */ + case CHAR_MAX + 23: /* --reference */ string_list_append (&files_for_vc_mtime, optarg); break; - case CHAR_MAX + 23: /* --no-git */ + case CHAR_MAX + 24: /* --no-git */ xgettext_no_git = true; break; - case CHAR_MAX + 24: /* --generated */ + case CHAR_MAX + 25: /* --generated */ gl_set_add (generated_files, optarg); break; @@ -1212,6 +1237,9 @@ Operation mode:\n")); (ellipsis-unicode, space-ellipsis,\n\ quote-unicode, bullet-unicode)\n")); printf (_("\ + --no-check=NAME don't perform syntax check on messages\n\ + (url, email)\n")); + printf (_("\ --sentence-end=TYPE type describing the end of sentence\n\ (single-space, which is the default, \n\ or double-space)\n")); diff --git a/gettext-tools/src/xgettext.h b/gettext-tools/src/xgettext.h index 8d0af3bcc..6511d7865 100644 --- a/gettext-tools/src/xgettext.h +++ b/gettext-tools/src/xgettext.h @@ -1,5 +1,5 @@ /* xgettext common functions. - Copyright (C) 2001-2003, 2005-2006, 2008-2009, 2011, 2013-2014, 2018, 2020, 2023 Free Software Foundation, Inc. + Copyright (C) 2001-2026 Free Software Foundation, Inc. Written by Peter Miller and Bruno Haible , 2001. @@ -53,6 +53,7 @@ extern int xgettext_omit_header; /* Be more verbose. */ extern int verbose; +/* Syntax checks enabled through a command-line option or by default. */ extern enum is_syntax_check default_syntax_check[NSYNTAXCHECKS]; diff --git a/gettext-tools/tests/xgettext-14 b/gettext-tools/tests/xgettext-14 index 8035820a9..3afeaa195 100755 --- a/gettext-tools/tests/xgettext-14 +++ b/gettext-tools/tests/xgettext-14 @@ -20,7 +20,7 @@ EOF : ${XGETTEXT=xgettext} LANGUAGE= LC_ALL=C ${XGETTEXT} --omit-header --add-comments --check=ellipsis-unicode -d xg-ellipsis-u.tmp xg-ellipsis-u.c 2>xg-ellipsis-u.err -test `grep -c 'ASCII ellipsis' xg-ellipsis-u.err` = 4 || Exit 1 +test `grep -c 'ASCII ellipsis' xg-ellipsis-u.err` = 3 || Exit 1 LANGUAGE= LC_ALL=C ${XGETTEXT} --omit-header --add-comments --check=ellipsis-unicode --sentence-end=double-space -d xg-ellipsis-ud.tmp xg-ellipsis-u.c 2>xg-ellipsis-ud.err @@ -40,7 +40,7 @@ EOF LANGUAGE= LC_ALL=C ${XGETTEXT} --omit-header --add-comments --check=space-ellipsis -d xg-space-e.tmp xg-space-e.c 2>xg-space-e.err -test `grep -c 'space before ellipsis' xg-space-e.err` = 3 || Exit 1 +test `grep -c 'space before ellipsis' xg-space-e.err` = 2 || Exit 1 # --check=quote-unicode cat <<\EOF > xg-quote-u.c diff --git a/gettext-tools/tests/xgettext-20 b/gettext-tools/tests/xgettext-20 index e96e2de74..2177cda8e 100755 --- a/gettext-tools/tests/xgettext-20 +++ b/gettext-tools/tests/xgettext-20 @@ -9,16 +9,67 @@ cat <<\EOF > xg-test20.c gettext ("Report bugs to "); gettext ("Report bugs to: bug-foobar@gnu.org"); gettext ("Report bugs in the bug tracker at "); + /* xgettext: no-email-check */ + gettext ("M2: Report bugs to "); + /* xgettext: no-email-check */ + gettext ("M2: Report bugs to: bug-foobar@gnu.org"); + /* xgettext: no-url-check */ + gettext ("M2: Report bugs in the bug tracker at "); + /* xgettext: no-url-check */ + gettext ("M3: Report bugs to: bug-foobar@gnu.org"); EOF : ${XGETTEXT=xgettext} LANGUAGE= LC_ALL=C ${XGETTEXT} --omit-header --add-comments -d xg-test20.tmp xg-test20.c 2>xg-test20.err \ || Exit 1 +cat xg-test20.err; echo -if grep "xg-test20.c:1:.*No-embedded-URLs.html" xg-test20.err; then +if grep "xg-test20.c:1:.*No-embedded-URLs.html" xg-test20.err >/dev/null; then Exit 1 fi -grep "xg-test20.c:2:.*No-embedded-URLs.html" xg-test20.err || Exit 1 -grep "xg-test20.c:3:.*No-embedded-URLs.html" xg-test20.err || Exit 1 -grep "xg-test20.c:4:.*No-embedded-URLs.html" xg-test20.err || Exit 1 +grep "xg-test20.c:2:.*No-embedded-URLs.html" xg-test20.err >/dev/null || Exit 1 +grep "xg-test20.c:3:.*No-embedded-URLs.html" xg-test20.err >/dev/null || Exit 1 +grep "xg-test20.c:4:.*No-embedded-URLs.html" xg-test20.err >/dev/null || Exit 1 + +if grep "xg-test20.c:6:.*No-embedded-URLs.html" xg-test20.err >/dev/null; then + Exit 1 +fi +if grep "xg-test20.c:8:.*No-embedded-URLs.html" xg-test20.err >/dev/null; then + Exit 1 +fi +if grep "xg-test20.c:10:.*No-embedded-URLs.html" xg-test20.err >/dev/null; then + Exit 1 +fi + +grep "xg-test20.c:12:.*No-embedded-URLs.html" xg-test20.err >/dev/null || Exit 1 + +# Likewise, with --no-check=url option: + +LANGUAGE= LC_ALL=C ${XGETTEXT} --omit-header --add-comments --no-check=url -d xg-test20.tmp xg-test20.c 2>xg-test20a.err \ + || Exit 1 +cat xg-test20a.err; echo + +grep "xg-test20.c:2:.*No-embedded-URLs.html" xg-test20a.err >/dev/null || Exit 1 +grep "xg-test20.c:3:.*No-embedded-URLs.html" xg-test20a.err >/dev/null || Exit 1 +if grep "xg-test20.c:4:.*No-embedded-URLs.html" xg-test20a.err >/dev/null; then + Exit 1 +fi +grep "xg-test20.c:12:.*No-embedded-URLs.html" xg-test20a.err >/dev/null || Exit 1 + +# Likewise, with --no-check=email option: + +LANGUAGE= LC_ALL=C ${XGETTEXT} --omit-header --add-comments --no-check=email -d xg-test20.tmp xg-test20.c 2>xg-test20b.err \ + || Exit 1 +cat xg-test20b.err; echo + +if grep "xg-test20.c:2:.*No-embedded-URLs.html" xg-test20b.err >/dev/null; then + Exit 1 +fi +if grep "xg-test20.c:3:.*No-embedded-URLs.html" xg-test20b.err >/dev/null; then + Exit 1 +fi +grep "xg-test20.c:4:.*No-embedded-URLs.html" xg-test20b.err >/dev/null || Exit 1 +if grep "xg-test20.c:12:.*No-embedded-URLs.html" xg-test20b.err >/dev/null; then + Exit 1 +fi