* gettext-tools/src/xg-message.h (remember_a_message): Add pluralp argument.
* gettext-tools/src/xg-message.c (remember_a_message): Likewise. Signal a
warning when the same msgid is used with and without plural.
* gettext-tools/src/xg-arglist-parser.c (arglist_parser_done): Update
remember_a_message invocation.
* gettext-tools/src/xgettext.c (xgettext_its_extract_callback): Update
remember_a_message invocations.
* gettext-tools/src/x-awk.c (extract_parenthesized): Likewise.
* gettext-tools/src/x-c.c (extract_parenthesized): Likewise.
* gettext-tools/src/x-csharp.c (extract_parenthesized): Likewise.
* gettext-tools/src/x-desktop.c (extract_desktop_handle_pair): Likewise.
* gettext-tools/src/x-elisp.c (read_object): Likewise.
* gettext-tools/src/x-java.c (extract_parenthesized): Likewise.
* gettext-tools/src/x-javascript.c (extract_balanced): Likewise.
* gettext-tools/src/x-librep.c (read_object): Likewise.
* gettext-tools/src/x-lisp.c (read_object): Likewise.
* gettext-tools/src/x-lua.c (extract_balanced): Likewise.
* gettext-tools/src/x-perl.c (extract_variable, interpolate_keywords,
extract_balanced): Likewise.
* gettext-tools/src/x-php.c (extract_balanced): Likewise.
* gettext-tools/src/x-python.c (extract_balanced): Likewise.
* gettext-tools/src/x-rst.c (extract_rst, extract_rsj): Likewise.
* gettext-tools/src/x-scheme.c (read_object): Likewise.
* gettext-tools/src/x-sh.c (read_word, read_command): Likewise.
* gettext-tools/src/x-tcl.c (read_command): Likewise.
* gettext-tools/src/x-vala.c (extract_balanced): Likewise.
* gettext-tools/src/x-smalltalk.c (phase3_pushback, phase3_pushback_length): New
variables.
(phase3_get): Renamed from x_smalltalk_lex. Handle pushback.
(phase3_unget): New function.
(extract_smalltalk): Update remember_a_message invocations through a lookahead
of one token.
* gettext-tools/src/x-ycp.c (phase8_pushback, phase8_pushback_length): New
variables.
(phase8_get): Handle pushback.
(phase8_unget): New function.
(extract_parenthesized): Update remember_a_message invocations through a
lookahead of one token.
* gettext-tools/tests/xgettext-15: New file.
* gettext-tools/tests/Makefile.am (TESTS): Add it.
/* xgettext awk backend.
- Copyright (C) 2002-2003, 2005-2009, 2018 Free Software Foundation, Inc.
+ Copyright (C) 2002-2003, 2005-2009, 2018-2019 Free Software Foundation, Inc.
This file was written by Bruno Haible <haible@clisp.cons.org>, 2002.
pos.line_number = token.line_number;
if (extract_all)
- remember_a_message (mlp, NULL, token.string, false, inner_context,
- &pos, NULL, savable_comment, false);
+ remember_a_message (mlp, NULL, token.string, false, false,
+ inner_context, &pos,
+ NULL, savable_comment, false);
else
{
mixed_string_ty *ms =
pos.file_name = logical_file_name;
pos.line_number = token.line_number;
- remember_a_message (mlp, NULL, token.string, false, inner_context,
- &pos, NULL, savable_comment, false);
+ remember_a_message (mlp, NULL, token.string, false, false,
+ inner_context, &pos,
+ NULL, savable_comment, false);
}
next_is_argument = false;
next_context_iter = null_context_list_iterator;
/* xgettext C/C++/ObjectiveC backend.
- Copyright (C) 1995-1998, 2000-2009, 2012-2015, 2018 Free Software Foundation, Inc.
+ Copyright (C) 1995-1998, 2000-2009, 2012-2015, 2018-2019 Free Software Foundation, Inc.
This file was written by Peter Miller <millerp@canb.auug.org.au>
{
char *string = mixed_string_contents (token.mixed_string);
mixed_string_free (token.mixed_string);
- remember_a_message (mlp, NULL, string, true, inner_context,
- &token.pos, NULL, token.comment, false);
+ remember_a_message (mlp, NULL, string, true, false,
+ inner_context, &token.pos,
+ NULL, token.comment, false);
}
else
arglist_parser_remember (argparser, arg, token.mixed_string,
{
char *string = mixed_string_contents (token.mixed_string);
mixed_string_free (token.mixed_string);
- remember_a_message (mlp, NULL, string, true, inner_context,
- &pos, NULL, token.comment, true);
+ remember_a_message (mlp, NULL, string, true, false,
+ inner_context, &pos,
+ NULL, token.comment, true);
}
else
arglist_parser_remember (argparser, arg, token.mixed_string,
remember_a_message (extract_reader->mlp, NULL,
desktop_unescape_string (value, is_list), false,
- null_context, key_pos,
+ false, null_context, key_pos,
NULL, savable_comment, false);
}
savable_comment_reset ();
pos.file_name = logical_file_name;
pos.line_number = op->line_number_at_start;
remember_a_message (mlp, NULL, string_of_object (op), false,
- null_context, &pos, NULL, savable_comment,
- false);
+ false, null_context, &pos,
+ NULL, savable_comment, false);
}
last_non_comment_line = line_number;
return;
{
char *string = mixed_string_contents (token.mixed_string);
mixed_string_free (token.mixed_string);
- remember_a_message (mlp, NULL, string, true, inner_context,
- &pos, NULL, token.comment, true);
+ remember_a_message (mlp, NULL, string, true, false,
+ inner_context, &pos,
+ NULL, token.comment, true);
}
else
arglist_parser_remember (argparser, arg, token.mixed_string,
{
char *string = mixed_string_contents (token.mixed_string);
mixed_string_free (token.mixed_string);
- remember_a_message (mlp, NULL, string, true, inner_context,
- &pos, NULL, token.comment, true);
+ remember_a_message (mlp, NULL, string, true, false,
+ inner_context, &pos,
+ NULL, token.comment, true);
}
else
arglist_parser_remember (argparser, arg, token.mixed_string,
pos.file_name = logical_file_name;
pos.line_number = op->line_number_at_start;
remember_a_message (mlp, NULL, string_of_object (op), false,
- null_context, &pos, NULL, savable_comment,
- false);
+ false, null_context, &pos,
+ NULL, savable_comment, false);
}
last_non_comment_line = line_number;
return;
pos.file_name = logical_file_name;
pos.line_number = op->line_number_at_start;
remember_a_message (mlp, NULL, string_of_object (op), false,
- null_context, &pos,
+ false, null_context, &pos,
NULL, savable_comment, false);
}
last_non_comment_line = line_number;
pos.line_number = token.line_number;
if (extract_all)
- remember_a_message (mlp, NULL, token.string, false, inner_context,
- &pos, NULL, token.comment, false);
+ remember_a_message (mlp, NULL, token.string, false, false,
+ inner_context, &pos,
+ NULL, token.comment, false);
else
{
mixed_string_ty *ms =
pos.file_name = logical_file_name;
remember_a_message (mlp, NULL, xstrdup (t1->string),
- true, context, &pos, NULL,
- savable_comment, true);
+ true, false, context, &pos,
+ NULL, savable_comment, true);
free_token (t2);
free_token (t1);
}
buffer[bufpos] = '\0';
token.string = xstrdup (buffer);
extract_quotelike_pass3 (&token, EXIT_FAILURE);
- remember_a_message (mlp, NULL, token.string, true, context, &pos,
- NULL, savable_comment, true);
+ remember_a_message (mlp, NULL, token.string, true, false, context,
+ &pos, NULL, savable_comment, true);
/* FALLTHROUGH */
default:
context = null_context;
pos.file_name = logical_file_name;
pos.line_number = tp->line_number;
- remember_a_message (mlp, NULL, string, true, inner_context, &pos,
- NULL, tp->comment, true);
+ remember_a_message (mlp, NULL, string, true, false, inner_context,
+ &pos, NULL, tp->comment, true);
}
else if (!skip_until_comma)
{
/* xgettext PHP backend.
- Copyright (C) 2001-2003, 2005-2010, 2014, 2018 Free Software Foundation, Inc.
+ Copyright (C) 2001-2003, 2005-2010, 2014, 2018-2019 Free Software Foundation, Inc.
This file was written by Bruno Haible <bruno@clisp.org>, 2002.
pos.line_number = token.line_number;
if (extract_all)
- remember_a_message (mlp, NULL, token.string, false, inner_context,
- &pos, NULL, token.comment, false);
+ remember_a_message (mlp, NULL, token.string, false, false,
+ inner_context, &pos,
+ NULL, token.comment, false);
else
{
mixed_string_ty *ms =
{
char *string = mixed_string_contents (token.mixed_string);
mixed_string_free (token.mixed_string);
- remember_a_message (mlp, NULL, string, true, inner_context,
- &pos, NULL, token.comment, true);
+ remember_a_message (mlp, NULL, string, true, false,
+ inner_context, &pos,
+ NULL, token.comment, true);
}
else
arglist_parser_remember (argparser, arg, token.mixed_string,
/* xgettext RST/RSJ backend.
- Copyright (C) 2001-2003, 2005-2009, 2018 Free Software Foundation, Inc.
+ Copyright (C) 2001-2003, 2005-2009, 2018-2019 Free Software Foundation, Inc.
This file was written by Bruno Haible <haible@clisp.cons.org>, 2001.
pos.file_name = location;
pos.line_number = (size_t)(-1);
- remember_a_message (mlp, NULL, msgid, false, null_context, &pos, NULL,
- NULL, false);
+ remember_a_message (mlp, NULL, msgid, false, false, null_context, &pos,
+ NULL, NULL, false);
/* Here c is the last read character: EOF or '\n'. */
if (c == EOF)
pos.file_name = location;
pos.line_number = (size_t)(-1);
- remember_a_message (mlp, NULL, msgid, true, null_context,
- &pos, NULL, NULL, false);
+ remember_a_message (mlp, NULL, msgid, true, false,
+ null_context, &pos,
+ NULL, NULL, false);
/* Parse a comma. */
c = phase2_getc ();
pos.file_name = logical_file_name;
pos.line_number = op->line_number_at_start;
remember_a_message (mlp, NULL, string_of_object (op), false,
- null_context, &pos, NULL, savable_comment,
- false);
+ false, null_context, &pos,
+ NULL, savable_comment, false);
}
last_non_comment_line = line_number;
return;
string.chars[string.charcount++] = (unsigned char) c;
}
remember_a_message (mlp, NULL, string_of_token (&string),
- false, context, &pos, NULL,
- savable_comment, false);
+ false, false, context, &pos,
+ NULL, savable_comment, false);
free_token (&string);
error_with_progname = false;
pos.file_name = logical_file_name;
pos.line_number = inner.line_number_at_start;
remember_a_message (mlp, NULL, string_of_word (&inner), false,
- inner_context, &pos, NULL, savable_comment,
- false);
+ false, inner_context, &pos,
+ NULL, savable_comment, false);
}
}
/* xgettext Smalltalk backend.
- Copyright (C) 2002-2003, 2005-2009, 2011, 2018 Free Software Foundation, Inc.
+ Copyright (C) 2002-2003, 2005-2009, 2011, 2018-2019 Free Software Foundation, Inc.
This file was written by Bruno Haible <haible@clisp.cons.org>, 2002.
/* 3. Combine "# string_literal" and "# symbol" to a single token. */
+static token_ty phase3_pushback[1];
+static int phase3_pushback_length;
+
static void
-x_smalltalk_lex (token_ty *tp)
+phase3_get (token_ty *tp)
{
+ if (phase3_pushback_length)
+ {
+ *tp = phase3_pushback[--phase3_pushback_length];
+ return;
+ }
+
phase2_get (tp);
if (tp->type == token_type_uniq)
{
}
}
+/* Supports only one pushback token. */
+static void
+phase3_unget (token_ty *tp)
+{
+ if (tp->type != token_type_eof)
+ {
+ if (phase3_pushback_length == SIZEOF (phase3_pushback))
+ abort ();
+ phase3_pushback[phase3_pushback_length++] = *tp;
+ }
+}
+
/* ========================= Extracting strings. ========================== */
{
token_ty token;
- x_smalltalk_lex (&token);
+ phase3_get (&token);
switch (token.type)
{
lex_pos_ty pos;
pos.file_name = logical_file_name;
pos.line_number = token.line_number;
- remember_a_message (mlp, NULL, token.string, false,
+ remember_a_message (mlp, NULL, token.string, false, false,
null_context, &pos, NULL, savable_comment,
false);
state = 0;
if (state == 3)
{
lex_pos_ty pos;
+ token_ty token2;
+
pos.file_name = logical_file_name;
pos.line_number = token.line_number;
- plural_mp = remember_a_message (mlp, NULL, token.string, false,
- null_context, &pos,
- NULL, savable_comment, false);
+
+ phase3_get (&token2);
+
+ plural_mp =
+ remember_a_message (mlp, NULL, token.string, false,
+ token2.type == token_type_symbol
+ && strcmp (token.string, "plural:") == 0,
+ null_context, &pos,
+ NULL, savable_comment, false);
+
+ phase3_unget (&token2);
+
state = 4;
break;
}
pos.file_name = logical_file_name;
pos.line_number = inner.line_number_at_start;
remember_a_message (mlp, NULL, string_of_word (&inner), false,
- inner_context, &pos,
+ false, inner_context, &pos,
NULL, savable_comment, false);
}
}
{
char *string = mixed_string_contents (token.mixed_string);
mixed_string_free (token.mixed_string);
- remember_a_message (mlp, NULL, string, true, inner_context,
- &pos, NULL, token.comment, false);
+ remember_a_message (mlp, NULL, string, true, false,
+ inner_context, &pos,
+ NULL, token.comment, false);
}
else
{
/* xgettext YCP backend.
- Copyright (C) 2001-2003, 2005-2009, 2011, 2018 Free Software Foundation, Inc.
+ Copyright (C) 2001-2003, 2005-2009, 2011, 2018-2019 Free Software Foundation, Inc.
This file was written by Bruno Haible <haible@clisp.cons.org>, 2001.
/* Concatenate adjacent string literals to form single string literals.
(See libycp/src/parser.yy, rule 'string' vs. terminal 'STRING'.) */
+static token_ty phase8_pushback[1];
+static int phase8_pushback_length;
+
static void
phase8_get (token_ty *tp)
{
+ if (phase8_pushback_length)
+ {
+ *tp = phase8_pushback[--phase8_pushback_length];
+ return;
+ }
phase5_get (tp);
if (tp->type != token_type_string_literal)
return;
}
}
+/* Supports only one pushback token. */
+static void
+phase8_unget (token_ty *tp)
+{
+ if (tp->type != token_type_eof)
+ {
+ if (phase8_pushback_length == SIZEOF (phase8_pushback))
+ abort ();
+ phase8_pushback[phase8_pushback_length++] = *tp;
+ }
+}
+
/* ========================= Extracting strings. ========================== */
if (plural_state == 0)
{
/* Seen an msgid. */
- plural_mp = remember_a_message (mlp, NULL, token.string,
- false, inner_context, &pos,
- NULL, token.comment, false);
+ token_ty token2;
+
+ if (in_i18n)
+ phase8_get (&token2);
+ else
+ phase5_get (&token2);
+
+ plural_mp =
+ remember_a_message (mlp, NULL, token.string, false,
+ token2.type == token_type_comma,
+ inner_context, &pos,
+ NULL, token.comment, false);
+
+ if (in_i18n)
+ phase8_unget (&token2);
+ else
+ phase5_unget (&token2);
+
plural_state = 1;
state = 2;
}
/* Resolving ambiguity of argument lists: Progressive parsing of an
argument list, keeping track of all possibilities.
- Copyright (C) 2001-2018 Free Software Foundation, Inc.
+ Copyright (C) 2001-2019 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
}
mp = remember_a_message (ap->mlp, best_msgctxt, best_msgid, true,
+ best_msgid_plural != NULL,
msgid_context,
&best_cp->msgid_pos,
NULL, best_cp->msgid_comment,
/* Extracting a message. Accumulating the message list.
- Copyright (C) 2001-2018 Free Software Foundation, Inc.
+ Copyright (C) 2001-2019 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
message_ty *
remember_a_message (message_list_ty *mlp, char *msgctxt, char *msgid,
- bool is_utf8, flag_context_ty context, lex_pos_ty *pos,
+ bool is_utf8, bool pluralp, flag_context_ty context,
+ lex_pos_ty *pos,
const char *extracted_comment,
refcounted_string_list_ty *comment, bool comment_is_utf8)
{
mp = message_list_search (mlp, msgctxt, msgid);
if (mp != NULL)
{
+ if (pluralp != (mp->msgid_plural != NULL))
+ {
+ lex_pos_ty pos1;
+ lex_pos_ty pos2;
+ char buffer1[21];
+ char buffer2[21];
+
+ if (pluralp)
+ {
+ pos1 = mp->pos;
+ pos2 = *pos;
+ }
+ else
+ {
+ pos1 = *pos;
+ pos2 = mp->pos;
+ }
+
+ if (pos1.line_number == (size_t)(-1))
+ buffer1[0] = '\0';
+ else
+ sprintf (buffer1, ":%ld", (long) pos1.line_number);
+ if (pos2.line_number == (size_t)(-1))
+ buffer2[0] = '\0';
+ else
+ sprintf (buffer2, ":%ld", (long) pos2.line_number);
+ multiline_warning (xstrdup (_("warning: ")),
+ xasprintf ("%s\n%s\n%s\n%s\n",
+ xasprintf (_("msgid '%s' is used without plural and with plural."),
+ msgid),
+ xasprintf (_("%s%s: Here is the occurrence without plural."),
+ pos1.file_name, buffer1),
+ xasprintf (_("%s%s: Here is the occurrence with plural."),
+ pos2.file_name, buffer2),
+ xstrdup (_("Workaround: If the msgid is a sentence, change the wording of the sentence; otherwise, use contexts for disambiguation."))));
+ }
+
if (msgctxt != NULL)
free (msgctxt);
free (msgid);
/* Extracting a message. Accumulating the message list.
- Copyright (C) 2001-2018 Free Software Foundation, Inc.
+ Copyright (C) 2001-2019 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
MSGID must be a malloc()ed string; its ownership is passed to the callee.
IS_UTF8 must be true if MSGCTXT and MSGID have already been converted to
UTF-8.
+ PLURALP must be true if and only if a call to remember_a_message_plural will
+ follow.
POS->file_name must be allocated with indefinite extent.
EXTRACTED_COMMENT is a comment that needs to be copied into the POT file,
or NULL.
char *msgctxt,
char *msgid,
bool is_utf8,
+ bool pluralp,
flag_context_ty context,
lex_pos_ty *pos,
const char *extracted_comment,
message = remember_a_message (mlp,
msgctxt == NULL ? NULL : xstrdup (msgctxt),
xstrdup (msgid),
- false,
+ false, false,
null_context, pos,
extracted_comment, NULL, false);
recode-sr-latin-1 recode-sr-latin-2 \
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-13 xgettext-14 xgettext-15 \
xgettext-appdata-1 \
xgettext-awk-1 xgettext-awk-2 \
xgettext-c-2 xgettext-c-3 xgettext-c-4 xgettext-c-5 xgettext-c-6 \
--- /dev/null
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test for a warning when the same msgid is used with and without msgid_plural.
+# <https://savannah.gnu.org/bugs/?35027>
+# <https://savannah.gnu.org/bugs/?56456>
+# <https://savannah.gnu.org/bugs/?56919>
+
+# Case when the message with plural appears first.
+
+cat <<\EOF > xg-test15-1.py
+ngettext("Language", "Languages", n)
+_("Language")
+EOF
+
+: ${XGETTEXT=xgettext}
+LANGUAGE= LC_ALL=C ${XGETTEXT} --omit-header --add-comments -d xg-test15-1.tmp xg-test15-1.py 2>xg-test15-1.err || Exit 1
+LC_ALL=C tr -d '\r' < xg-test15-1.tmp.po > xg-test15-1.po || Exit 1
+
+grep "is used without plural and with plural" xg-test15-1.err || Exit 1
+
+cat <<\EOF > xg-test15-1.ok
+#: xg-test15-1.py:1 xg-test15-1.py:2
+msgid "Language"
+msgid_plural "Languages"
+msgstr[0] ""
+msgstr[1] ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-test15-1.ok xg-test15-1.po || Exit 1
+
+# Case when the message without plural appears first.
+
+cat <<\EOF > xg-test15-2.py
+_("Language")
+ngettext("Language", "Languages", n)
+EOF
+
+: ${XGETTEXT=xgettext}
+LANGUAGE= LC_ALL=C ${XGETTEXT} --omit-header --add-comments -d xg-test15-2.tmp xg-test15-2.py 2>xg-test15-2.err || Exit 1
+LC_ALL=C tr -d '\r' < xg-test15-2.tmp.po > xg-test15-2.po || Exit 1
+
+grep "is used without plural and with plural" xg-test15-2.err || Exit 1
+
+cat <<\EOF > xg-test15-2.ok
+#: xg-test15-2.py:1 xg-test15-2.py:2
+msgid "Language"
+msgid_plural "Languages"
+msgstr[0] ""
+msgstr[1] ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-test15-2.ok xg-test15-2.po || Exit 1