From: Bruno Haible Date: Sun, 25 Aug 2019 17:17:21 +0000 (+0200) Subject: Add support for Java format strings in the Formatter syntax. X-Git-Tag: v0.21~171 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c2758ebe0a99fedc2063bda6ebbea0d1df8cd934;p=thirdparty%2Fgettext.git Add support for Java format strings in the Formatter syntax. * gettext-tools/src/message.h (format_type): Add format_java_printf. (NFORMATS): Increment. * gettext-tools/src/message.c (format_language, format_language_pretty): Add entry for format_java_printf. * gettext-tools/src/format.h (formatstring_java_printf): New declaration. * gettext-tools/src/format-java.c: Update comments. * gettext-tools/src/format-java-printf.c: New file. * gettext-tools/src/format.c (formatstring_parsers): Add entry for format_java_printf. * gettext-tools/src/x-java.h (SCANNERS_JAVA): Use formatstring_java_printf. * gettext-tools/src/x-java.c (init_flag_table_java): Add entries relevant to format strings in Formatter syntax. * gettext-tools/src/xgettext.c (xgettext_record_flag): Add support for format_java_printf. * gettext-tools/src/FILES: Add format-java-printf.c. * gettext-tools/src/Makefile.am (FORMAT_SOURCE): Add format-java-printf.c. * gettext-tools/woe32dll/gettextsrc-exports.c: Export formatstring_java_printf. * gettext-tools/libgettextpo/Makefile.am (libgettextpo_la_AUXSOURCES): Likewise. * gettext-tools/tests/xgettext-6 (xg-test6.java): Add test for recognition of format strings in Formatter syntax. * gettext-tools/tests/format-java-printf-1: New file. * gettext-tools/tests/format-java-printf-2: New file. * gettext-tools/tests/Makefile.am (TESTS): Add them. * gettext-tools/tests/lang-java: Add two uses of String.format to the program. * gettext-tools/doc/gettext.texi (PO Files): Document java-printf-format and no-java-printf-format. (java-format): Describe both kinds of format strings. Update URLs. (Java): Mention the second kind of format string as well. * NEWS: Mention the improvement. --- diff --git a/NEWS b/NEWS index e7fae1389..c01ed071e 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,9 @@ Version 0.21 - August 2019 o xgettext now recognizes 'gettext' program invocations with the '-e' option, such as gettext -e 'some\nstring\n' + - Java: + xgettext now recognizes format strings in the Formatter syntax. They + are marked as 'java-printf-format' in POT and PO files. - Desktop Entry: The value of the 'Icon' property is no longer extracted into the POT file by xgettext. The documentation explains how to localize icons. diff --git a/gettext-tools/doc/gettext.texi b/gettext-tools/doc/gettext.texi index 87c903af2..20316a5ea 100644 --- a/gettext-tools/doc/gettext.texi +++ b/gettext-tools/doc/gettext.texi @@ -1615,7 +1615,13 @@ Likewise for Smalltalk, see @ref{smalltalk-format}. @kwindex java-format@r{ flag} @itemx no-java-format @kwindex no-java-format@r{ flag} -Likewise for Java, see @ref{java-format}. +Likewise for Java @code{MessageFormat} format strings, see @ref{java-format}. + +@item java-printf-format +@kwindex java-printf-format@r{ flag} +@itemx no-java-printf-format +@kwindex no-java-printf-format@r{ flag} +Likewise for Java @code{printf} format strings, see @ref{java-format}. @item csharp-format @kwindex csharp-format@r{ flag} @@ -9203,11 +9209,20 @@ or a nonzero digit (@samp{1} to @samp{9}). @node java-format @subsection Java Format Strings +There are two kinds of format strings in Java: those acceptable to the +@code{MessageFormat.format} function, labelled as @samp{java-format}, +and those acceptable to the @code{String.format} and +@code{PrintStream.printf} functions, labelled as @samp{java-printf-format}. + Java format strings are described in the JDK documentation for class @code{java.text.MessageFormat}, -@uref{http://java.sun.com/j2se/1.4/docs/api/java/text/MessageFormat.html}. +@uref{https://docs.oracle.com/javase/7/docs/api/java/text/MessageFormat.html}. See also the ICU documentation -@uref{http://oss.software.ibm.com/icu/apiref/classMessageFormat.html}. +@uref{http://icu-project.org/apiref/icu4j/com/ibm/icu/text/MessageFormat.html}. + +Java @code{printf} format strings are described in the JDK documentation +for class @code{java.util.Formatter}, +@uref{https://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html}. @node csharp-format @subsection C# Format Strings @@ -10319,6 +10334,7 @@ automatic @item Formatting with positions @code{MessageFormat.format "@{1,number@} @{0,number@}"} +or @code{String.format "%2$d %1$d"} @item Portability fully portable diff --git a/gettext-tools/libgettextpo/Makefile.am b/gettext-tools/libgettextpo/Makefile.am index b4912cf0d..97139a662 100644 --- a/gettext-tools/libgettextpo/Makefile.am +++ b/gettext-tools/libgettextpo/Makefile.am @@ -73,6 +73,7 @@ libgettextpo_la_AUXSOURCES = \ ../src/format-librep.c \ ../src/format-scheme.c \ ../src/format-java.c \ + ../src/format-java-printf.c \ ../src/format-javascript.c \ ../src/format-csharp.c \ ../src/format-awk.c \ diff --git a/gettext-tools/src/FILES b/gettext-tools/src/FILES index 2bbeb01f0..52c1ad058 100644 --- a/gettext-tools/src/FILES +++ b/gettext-tools/src/FILES @@ -223,6 +223,7 @@ format-elisp.c Format string handling for Emacs Lisp. format-librep.c Format string handling for librep. format-scheme.c Format string handling for Scheme. format-java.c Format string handling for Java. +format-java-printf.c Format string handling for Java, printf syntax. format-csharp.c Format string handling for C#. format-awk.c Format string handling for awk. format-pascal.c Format string handling for Object Pascal. diff --git a/gettext-tools/src/Makefile.am b/gettext-tools/src/Makefile.am index 5b6c361af..b0894211d 100644 --- a/gettext-tools/src/Makefile.am +++ b/gettext-tools/src/Makefile.am @@ -129,6 +129,7 @@ FORMAT_SOURCE += \ format-librep.c \ format-scheme.c \ format-java.c \ + format-java-printf.c \ format-csharp.c \ format-awk.c \ format-pascal.c \ diff --git a/gettext-tools/src/format-java-printf.c b/gettext-tools/src/format-java-printf.c new file mode 100644 index 000000000..d0f133c1a --- /dev/null +++ b/gettext-tools/src/format-java-printf.c @@ -0,0 +1,725 @@ +/* Java printf format strings. + Copyright (C) 2001-2004, 2006-2007, 2009-2010, 2018-2019 Free Software + Foundation, Inc. + Written by Bruno Haible , 2001. + + 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 . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include "format.h" +#include "c-ctype.h" +#include "xalloc.h" +#include "xvasprintf.h" +#include "format-invalid.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* Java printf format strings are described in java/util/Formatter.html. + A directive + - starts with '%' or '%<' or '%m$' where m is a positive integer, + - is optionally followed by any of the characters '#', '0', '-', ' ', '+', + ',', '(', + - is optionally followed by a width specification: a nonempty digit sequence, + - is optionally followed by '.' and a precision specification: a nonempty + digit sequence, + - is finished by a specifier + - '%', 'n', that need no argument, + Restrictions: + - For '%': flags other than '-' are invalid, and a precision is + invalid. + - For 'n': flags, width, and precision are invalid. + - 'b', 'B', 'h', 'H', 's', 'S', that need a general argument. + Restrictions: + Flags other than '#' and '-' are invalid. + - 'c', 'C', that need a character argument, + Restrictions: + Flags other than '-' are invalid. + A precision is invalid. + - 'd', 'o', 'x', 'X', that need an integer argument, + Restrictions: + - For 'd': The flag '#' is invalid. + - For 'o', 'x', 'X': The flag ',' is invalid. + A precision is invalid. + - 'e', 'E', 'f', 'g', 'G', 'a', 'A', that need a floating-point argument, + Restrictions: + - For 'a', 'A': The flags ',', '(' are invalid. + - 't', 'T', followed by one of + 'H', 'I', 'k', 'l', 'M', 'S', 'L', 'N', 'p', 'z', 'Z', 's', 'Q', + 'B', 'b', 'h', 'A', 'a', 'C', 'Y', 'y', 'j', 'm', 'd', 'e', + 'R', 'T', 'r', 'D', 'F', 'c' + that need a date/time argument. + Restrictions: + Flags other than '-' are invalid. + A precision is invalid. + Numbered ('%m$') and unnumbered argument specifications can be mixed in the + same string. Numbered argument specifications have no influence on the + unnumbered argument counter. + */ + +enum format_arg_type +{ + FAT_NONE = 0, + /* Basic types */ + FAT_GENERAL = 1, + FAT_CHARACTER = 2, + FAT_INTEGER = 3, + FAT_FLOATINGPOINT = 4, + FAT_DATETIME = 5 +}; +#ifdef __cplusplus +typedef int format_arg_type_t; +#else +typedef enum format_arg_type format_arg_type_t; +#endif + +enum +{ + /* Flags */ + FAT_ALTERNATE = 1 << 0, /* '#' */ + FAT_ZERO_PADDED = 1 << 1, /* '0' */ + FAT_LEFT_JUSTIFIED = 1 << 2, /* '-' */ + FAT_SPACE_SIGN = 1 << 3, /* ' ' */ + FAT_SIGN = 1 << 4, /* '+' */ + FAT_OBEY_LOCALE = 1 << 5, /* ',' */ + FAT_MONETARY = 1 << 6, /* '(' */ + /* Width */ + FAT_WIDTH = 1 << 7, + /* Precision */ + FAT_PRECISION = 1 << 8, +}; + +struct numbered_arg +{ + unsigned int number; + format_arg_type_t type; +}; + +struct spec +{ + unsigned int directives; + unsigned int numbered_arg_count; + unsigned int allocated; + struct numbered_arg *numbered; +}; + +/* Locale independent test for a decimal digit. + Argument can be 'char' or 'unsigned char'. (Whereas the argument of + isdigit must be an 'unsigned char'.) */ +#undef isdigit +#define isdigit(c) ((unsigned int) ((c) - '0') < 10) + + +static int +numbered_arg_compare (const void *p1, const void *p2) +{ + unsigned int n1 = ((const struct numbered_arg *) p1)->number; + unsigned int n2 = ((const struct numbered_arg *) p2)->number; + + return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); +} + +#define INVALID_LAST_ARG(directive_number) \ + xasprintf (_("In the directive number %u, the reference to the argument of the previous directive is invalid."), directive_number) + +#define INVALID_PRECISION_MISSING(directive_number) \ + xasprintf (_("In the directive number %u, the precision is missing."), directive_number) + +#define INVALID_FLAG_FOR(directive_number,flag_char,conv_char) \ + xasprintf (_("In the directive number %u, the flag '%c' is invalid for the conversion '%c'."), directive_number, flag_char, conv_char) + +#define INVALID_WIDTH_FOR(directive_number,conv_char) \ + xasprintf (_("In the directive number %u, a width is invalid for the conversion '%c'."), directive_number, conv_char) + +#define INVALID_PRECISION_FOR(directive_number,conv_char) \ + xasprintf (_("In the directive number %u, a precision is invalid for the conversion '%c'."), directive_number, conv_char) + +#define INVALID_DATETIME_CONVERSION_SUFFIX(directive_number,conv_char,suffix_char) \ + (c_isprint (conv_char) \ + ? xasprintf (_("In the directive number %u, for the conversion '%c', the character '%c' is not a valid conversion suffix."), directive_number, conv_char, suffix_char) \ + : xasprintf (_("The character that terminates the directive number %u, for the conversion '%c', is not a valid conversion suffix."), directive_number, conv_char)) + +static void * +format_parse (const char *format, bool translated, char *fdi, + char **invalid_reason) +{ + const char *const format_start = format; + struct spec spec; + struct spec *result; + unsigned int unnumbered_arg_count; + unsigned int last_arg_number; + + spec.directives = 0; + spec.numbered_arg_count = 0; + spec.allocated = 0; + spec.numbered = NULL; + unnumbered_arg_count = 0; + last_arg_number = 0; + + for (; *format != '\0';) + if (*format++ == '%') + { + /* A directive. */ + unsigned int number = 0; + unsigned int flags; + format_arg_type_t type; + unsigned int invalid_flags; + + FDI_SET (format - 1, FMTDIR_START); + spec.directives++; + + if (*format == '<') + { + if (last_arg_number == 0) + { + *invalid_reason = INVALID_LAST_ARG (spec.directives); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + number = last_arg_number; + format++; + } + else if (isdigit (*format)) + { + const char *f = format; + unsigned int m = 0; + + do + { + m = 10 * m + (*f - '0'); + f++; + } + while (isdigit (*f)); + + if (*f == '$') + { + if (m == 0) + { + *invalid_reason = INVALID_ARGNO_0 (spec.directives); + FDI_SET (f, FMTDIR_ERROR); + goto bad_format; + } + number = m; + format = ++f; + } + } + + flags = 0; + + /* Parse flags. */ + for (;;) + { + if (*format == '#') + { + flags |= FAT_ALTERNATE; + format++; + } + else if (*format == '0') + { + flags |= FAT_ZERO_PADDED; + format++; + } + else if (*format == '-') + { + flags |= FAT_LEFT_JUSTIFIED; + format++; + } + else if (*format == ' ') + { + flags |= FAT_SPACE_SIGN; + format++; + } + else if (*format == '+') + { + flags |= FAT_SIGN; + format++; + } + else if (*format == ',') + { + flags |= FAT_OBEY_LOCALE; + format++; + } + else if (*format == '(') + { + flags |= FAT_MONETARY; + format++; + } + else + break; + } + + /* Parse width. */ + if (isdigit (*format)) + { + do format++; while (isdigit (*format)); + flags |= FAT_WIDTH; + } + + /* Parse precision. */ + if (*format == '.') + { + format++; + + if (!isdigit (*format)) + { + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = INVALID_PRECISION_MISSING (spec.directives); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + + do format++; while (isdigit (*format)); + flags |= FAT_PRECISION; + } + + /* Parse conversion. */ + switch (*format) + { + case '%': + type = FAT_NONE; + invalid_flags = (FAT_ALTERNATE | FAT_ZERO_PADDED | FAT_SPACE_SIGN + | FAT_SIGN | FAT_OBEY_LOCALE | FAT_MONETARY) + | FAT_PRECISION; + break; + case 'n': + type = FAT_NONE; + invalid_flags = (FAT_ALTERNATE | FAT_ZERO_PADDED | FAT_LEFT_JUSTIFIED + | FAT_SPACE_SIGN | FAT_SIGN | FAT_OBEY_LOCALE + | FAT_MONETARY) + | FAT_WIDTH | FAT_PRECISION; + break; + case 'b': case 'B': + case 'h': case 'H': + case 's': case 'S': + type = FAT_GENERAL; + invalid_flags = (FAT_ZERO_PADDED | FAT_SPACE_SIGN | FAT_SIGN + | FAT_OBEY_LOCALE | FAT_MONETARY); + break; + case 'c': case 'C': + type = FAT_CHARACTER; + invalid_flags = (FAT_ALTERNATE | FAT_ZERO_PADDED | FAT_SPACE_SIGN + | FAT_SIGN | FAT_OBEY_LOCALE | FAT_MONETARY) + | FAT_PRECISION; + break; + case 'd': + type = FAT_INTEGER; + invalid_flags = FAT_ALTERNATE | FAT_PRECISION; + break; + case 'o': case 'x': case 'X': + type = FAT_INTEGER; + invalid_flags = FAT_OBEY_LOCALE | FAT_PRECISION; + break; + case 'e': case 'E': + case 'f': + case 'g': case 'G': + type = FAT_FLOATINGPOINT; + invalid_flags = 0; + break; + case 'a': case 'A': + type = FAT_FLOATINGPOINT; + invalid_flags = FAT_OBEY_LOCALE | FAT_MONETARY; + break; + case 't': case 'T': + type = FAT_DATETIME; + invalid_flags = (FAT_ALTERNATE | FAT_ZERO_PADDED | FAT_SPACE_SIGN + | FAT_SIGN | FAT_OBEY_LOCALE | FAT_MONETARY) + | FAT_PRECISION; + break; + default: + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + INVALID_CONVERSION_SPECIFIER (spec.directives, *format); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + + /* Report invalid flags, width, precision. */ + invalid_flags &= flags; + if (invalid_flags & FAT_ALTERNATE) + { + *invalid_reason = INVALID_FLAG_FOR (spec.directives, '#', *format); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + if (invalid_flags & FAT_ZERO_PADDED) + { + *invalid_reason = INVALID_FLAG_FOR (spec.directives, '0', *format); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + if (invalid_flags & FAT_LEFT_JUSTIFIED) + { + *invalid_reason = INVALID_FLAG_FOR (spec.directives, '-', *format); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + if (invalid_flags & FAT_SPACE_SIGN) + { + *invalid_reason = INVALID_FLAG_FOR (spec.directives, ' ', *format); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + if (invalid_flags & FAT_SIGN) + { + *invalid_reason = INVALID_FLAG_FOR (spec.directives, '+', *format); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + if (invalid_flags & FAT_OBEY_LOCALE) + { + *invalid_reason = INVALID_FLAG_FOR (spec.directives, ',', *format); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + if (invalid_flags & FAT_MONETARY) + { + *invalid_reason = INVALID_FLAG_FOR (spec.directives, '(', *format); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + if (invalid_flags & FAT_WIDTH) + { + *invalid_reason = INVALID_WIDTH_FOR (spec.directives, *format); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + if (invalid_flags & FAT_PRECISION) + { + *invalid_reason = INVALID_PRECISION_FOR (spec.directives, *format); + FDI_SET (format, FMTDIR_ERROR); + goto bad_format; + } + + if (type == FAT_DATETIME) + { + format++; + + /* Parse conversion suffix. */ + switch (*format) + { + case 'H': case 'I': case 'k': case 'l': case 'M': case 'S': + case 'L': case 'N': case 'p': case 'z': case 'Z': case 's': + case 'Q': + case 'B': case 'b': case 'h': case 'A': case 'a': case 'C': + case 'Y': case 'y': case 'j': case 'm': case 'd': case 'e': + case 'R': case 'T': case 'r': case 'D': case 'F': case 'c': + break; + default: + if (*format == '\0') + { + *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); + FDI_SET (format - 1, FMTDIR_ERROR); + } + else + { + *invalid_reason = + INVALID_DATETIME_CONVERSION_SUFFIX (spec.directives, + format[-1], *format); + FDI_SET (format, FMTDIR_ERROR); + } + goto bad_format; + } + } + + if (type != FAT_NONE) + { + if (number == 0) + number = ++unnumbered_arg_count; + + if (spec.allocated == spec.numbered_arg_count) + { + spec.allocated = 2 * spec.allocated + 1; + spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); + } + spec.numbered[spec.numbered_arg_count].number = number; + spec.numbered[spec.numbered_arg_count].type = type; + spec.numbered_arg_count++; + + last_arg_number = number; + } + + FDI_SET (format, FMTDIR_END); + + format++; + } + + /* Sort the numbered argument array, and eliminate duplicates. */ + if (spec.numbered_arg_count > 1) + { + unsigned int i, j; + bool err; + + qsort (spec.numbered, spec.numbered_arg_count, + sizeof (struct numbered_arg), numbered_arg_compare); + + /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ + err = false; + for (i = j = 0; i < spec.numbered_arg_count; i++) + if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number) + { + enum format_arg_type type1 = spec.numbered[i].type; + enum format_arg_type type2 = spec.numbered[j-1].type; + enum format_arg_type type_both; + + if (type1 == type2) + type_both = type1; + else + { + /* Incompatible types. */ + type_both = FAT_NONE; + if (!err) + *invalid_reason = + INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number); + err = true; + } + + spec.numbered[j-1].type = type_both; + } + else + { + if (j < i) + { + spec.numbered[j].number = spec.numbered[i].number; + spec.numbered[j].type = spec.numbered[i].type; + } + j++; + } + spec.numbered_arg_count = j; + if (err) + /* *invalid_reason has already been set above. */ + goto bad_format; + } + + result = XMALLOC (struct spec); + *result = spec; + return result; + + bad_format: + if (spec.numbered != NULL) + free (spec.numbered); + return NULL; +} + +static void +format_free (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + if (spec->numbered != NULL) + free (spec->numbered); + free (spec); +} + +static int +format_get_number_of_directives (void *descr) +{ + struct spec *spec = (struct spec *) descr; + + return spec->directives; +} + +static bool +format_check (void *msgid_descr, void *msgstr_descr, bool equality, + formatstring_error_logger_t error_logger, + const char *pretty_msgid, const char *pretty_msgstr) +{ + struct spec *spec1 = (struct spec *) msgid_descr; + struct spec *spec2 = (struct spec *) msgstr_descr; + bool err = false; + + if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0) + { + unsigned int i, j; + unsigned int n1 = spec1->numbered_arg_count; + unsigned int n2 = spec2->numbered_arg_count; + + /* Check the argument names are the same. + Both arrays are sorted. We search for the first difference. */ + for (i = 0, j = 0; i < n1 || j < n2; ) + { + int cmp = (i >= n1 ? 1 : + j >= n2 ? -1 : + spec1->numbered[i].number > spec2->numbered[j].number ? 1 : + spec1->numbered[i].number < spec2->numbered[j].number ? -1 : + 0); + + if (cmp > 0) + { + if (error_logger) + error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"), + spec2->numbered[j].number, pretty_msgstr, + pretty_msgid); + err = true; + break; + } + else if (cmp < 0) + { + if (equality) + { + if (error_logger) + error_logger (_("a format specification for argument %u doesn't exist in '%s'"), + spec1->numbered[i].number, pretty_msgstr); + err = true; + break; + } + else + i++; + } + else + j++, i++; + } + /* Check the argument types are the same. */ + if (!err) + for (i = 0, j = 0; j < n2; ) + { + if (spec1->numbered[i].number == spec2->numbered[j].number) + { + if (spec1->numbered[i].type != spec2->numbered[j].type) + { + if (error_logger) + error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"), + pretty_msgid, pretty_msgstr, + spec2->numbered[j].number); + err = true; + break; + } + j++, i++; + } + else + i++; + } + } + + return err; +} + + +struct formatstring_parser formatstring_java_printf = +{ + format_parse, + format_free, + format_get_number_of_directives, + NULL, + format_check +}; + + +#ifdef TEST + +/* Test program: Print the argument list specification returned by + format_parse for strings read from standard input. */ + +#include + +static void +format_print (void *descr) +{ + struct spec *spec = (struct spec *) descr; + unsigned int i; + + if (spec == NULL) + { + printf ("INVALID"); + return; + } + + printf ("("); + for (i = 0; i < spec->numbered_arg_count; i++) + { + if (i > 0) + printf (" "); + switch (spec->numbered[i].type) + { + case FAT_GENERAL: + printf ("s"); + break; + case FAT_CHARACTER: + printf ("c"); + break; + case FAT_INTEGER: + printf ("d"); + break; + case FAT_FLOATINGPOINT: + printf ("f"); + break; + case FAT_DATETIME: + printf ("t"); + break; + default: + abort (); + } + } + printf (")"); +} + +int +main () +{ + for (;;) + { + char *line = NULL; + size_t line_size = 0; + int line_len; + char *invalid_reason; + void *descr; + + line_len = getline (&line, &line_size, stdin); + if (line_len < 0) + break; + if (line_len > 0 && line[line_len - 1] == '\n') + line[--line_len] = '\0'; + + invalid_reason = NULL; + descr = format_parse (line, false, NULL, &invalid_reason); + + format_print (descr); + printf ("\n"); + if (descr == NULL) + printf ("%s\n", invalid_reason); + + free (invalid_reason); + free (line); + } + + return 0; +} + +/* + * For Emacs M-x compile + * Local Variables: + * compile-command: "/bin/sh ../libtool --tag=CC --mode=link gcc -o a.out -static -O -g -Wall -I.. -I../gnulib-lib -I../../gettext-runtime/intl -DHAVE_CONFIG_H -DTEST format-java-printf.c ../gnulib-lib/libgettextlib.la" + * End: + */ + +#endif /* TEST */ diff --git a/gettext-tools/src/format-java.c b/gettext-tools/src/format-java.c index a06513d85..cd117850e 100644 --- a/gettext-tools/src/format-java.c +++ b/gettext-tools/src/format-java.c @@ -1,4 +1,4 @@ -/* Java format strings. +/* Java MessageFormat format strings. Copyright (C) 2001-2004, 2006-2007, 2009, 2019 Free Software Foundation, Inc. Written by Bruno Haible , 2001. @@ -34,7 +34,8 @@ #define _(str) gettext (str) -/* Java format strings are described in java/text/MessageFormat.html. +/* Java MessageFormat format strings are described in + java/text/MessageFormat.html. See also the ICU documentation class_MessageFormat.html. messageFormatPattern := string ( "{" messageFormatElement "}" string )* diff --git a/gettext-tools/src/format.c b/gettext-tools/src/format.c index 1f019e5d7..987c50dda 100644 --- a/gettext-tools/src/format.c +++ b/gettext-tools/src/format.c @@ -45,6 +45,7 @@ struct formatstring_parser *formatstring_parsers[NFORMATS] = /* format_scheme */ &formatstring_scheme, /* format_smalltalk */ &formatstring_smalltalk, /* format_java */ &formatstring_java, + /* format_java_printf */ &formatstring_java_printf, /* format_csharp */ &formatstring_csharp, /* format_awk */ &formatstring_awk, /* format_pascal */ &formatstring_pascal, diff --git a/gettext-tools/src/format.h b/gettext-tools/src/format.h index b48a1f9e7..0ae7007a6 100644 --- a/gettext-tools/src/format.h +++ b/gettext-tools/src/format.h @@ -1,5 +1,5 @@ /* Format strings. - Copyright (C) 2001-2010, 2012-2013, 2015 Free Software Foundation, Inc. + Copyright (C) 2001-2010, 2012-2013, 2015, 2019 Free Software Foundation, Inc. Written by Bruno Haible , 2001. This program is free software: you can redistribute it and/or modify @@ -106,6 +106,7 @@ extern DLL_VARIABLE struct formatstring_parser formatstring_librep; extern DLL_VARIABLE struct formatstring_parser formatstring_scheme; extern DLL_VARIABLE struct formatstring_parser formatstring_smalltalk; extern DLL_VARIABLE struct formatstring_parser formatstring_java; +extern DLL_VARIABLE struct formatstring_parser formatstring_java_printf; extern DLL_VARIABLE struct formatstring_parser formatstring_csharp; extern DLL_VARIABLE struct formatstring_parser formatstring_awk; extern DLL_VARIABLE struct formatstring_parser formatstring_pascal; diff --git a/gettext-tools/src/message.c b/gettext-tools/src/message.c index 1bcb2f833..05437eb15 100644 --- a/gettext-tools/src/message.c +++ b/gettext-tools/src/message.c @@ -1,5 +1,5 @@ /* GNU gettext - internationalization aids - Copyright (C) 1995-1998, 2000-2010, 2012-2013, 2015-2016 Free Software Foundation, Inc. + Copyright (C) 1995-1998, 2000-2010, 2012-2013, 2015-2016, 2019 Free Software Foundation, Inc. This file was written by Peter Miller @@ -45,6 +45,7 @@ const char *const format_language[NFORMATS] = /* format_scheme */ "scheme", /* format_smalltalk */ "smalltalk", /* format_java */ "java", + /* format_java_printf */ "java-printf", /* format_csharp */ "csharp", /* format_awk */ "awk", /* format_pascal */ "object-pascal", @@ -76,7 +77,8 @@ const char *const format_language_pretty[NFORMATS] = /* format_librep */ "librep", /* format_scheme */ "Scheme", /* format_smalltalk */ "Smalltalk", - /* format_java */ "Java", + /* format_java */ "Java MessageFormat", + /* format_java_printf */ "Java printf", /* format_csharp */ "C#", /* format_awk */ "awk", /* format_pascal */ "Object Pascal", diff --git a/gettext-tools/src/message.h b/gettext-tools/src/message.h index e85ed2b77..5ee97187c 100644 --- a/gettext-tools/src/message.h +++ b/gettext-tools/src/message.h @@ -1,5 +1,5 @@ /* GNU gettext - internationalization aids - Copyright (C) 1995-1998, 2000-2010, 2012-2013, 2015-2016 Free Software Foundation, Inc. + Copyright (C) 1995-1998, 2000-2010, 2012-2013, 2015-2016, 2019 Free Software Foundation, Inc. This file was written by Peter Miller @@ -54,6 +54,7 @@ enum format_type format_scheme, format_smalltalk, format_java, + format_java_printf, format_csharp, format_awk, format_pascal, @@ -72,7 +73,7 @@ enum format_type format_lua, format_javascript }; -#define NFORMATS 28 /* Number of format_type enum values. */ +#define NFORMATS 29 /* Number of format_type enum values. */ extern DLL_VARIABLE const char *const format_language[NFORMATS]; extern DLL_VARIABLE const char *const format_language_pretty[NFORMATS]; diff --git a/gettext-tools/src/x-java.c b/gettext-tools/src/x-java.c index e174bd066..62b5429fa 100644 --- a/gettext-tools/src/x-java.c +++ b/gettext-tools/src/x-java.c @@ -124,20 +124,35 @@ void init_flag_table_java () { xgettext_record_flag ("GettextResource.gettext:2:pass-java-format"); + xgettext_record_flag ("GettextResource.gettext:2:pass-java-printf-format"); xgettext_record_flag ("GettextResource.ngettext:2:pass-java-format"); + xgettext_record_flag ("GettextResource.ngettext:2:pass-java-printf-format"); xgettext_record_flag ("GettextResource.ngettext:3:pass-java-format"); + xgettext_record_flag ("GettextResource.ngettext:3:pass-java-printf-format"); xgettext_record_flag ("GettextResource.pgettext:3:pass-java-format"); + xgettext_record_flag ("GettextResource.pgettext:3:pass-java-printf-format"); xgettext_record_flag ("GettextResource.npgettext:3:pass-java-format"); + xgettext_record_flag ("GettextResource.npgettext:3:pass-java-printf-format"); xgettext_record_flag ("GettextResource.npgettext:4:pass-java-format"); + xgettext_record_flag ("GettextResource.npgettext:4:pass-java-printf-format"); xgettext_record_flag ("gettext:1:pass-java-format"); + xgettext_record_flag ("gettext:1:pass-java-printf-format"); xgettext_record_flag ("ngettext:1:pass-java-format"); + xgettext_record_flag ("ngettext:1:pass-java-printf-format"); xgettext_record_flag ("ngettext:2:pass-java-format"); + xgettext_record_flag ("ngettext:2:pass-java-printf-format"); xgettext_record_flag ("pgettext:2:pass-java-format"); + xgettext_record_flag ("pgettext:2:pass-java-printf-format"); xgettext_record_flag ("npgettext:2:pass-java-format"); + xgettext_record_flag ("npgettext:2:pass-java-printf-format"); xgettext_record_flag ("npgettext:3:pass-java-format"); + xgettext_record_flag ("npgettext:3:pass-java-printf-format"); xgettext_record_flag ("getString:1:pass-java-format"); + xgettext_record_flag ("getString:1:pass-java-printf-format"); xgettext_record_flag ("MessageFormat:1:java-format"); xgettext_record_flag ("MessageFormat.format:1:java-format"); + xgettext_record_flag ("String.format:1:java-printf-format"); + xgettext_record_flag ("printf:1:java-printf-format"); /* PrintStream.printf */ } diff --git a/gettext-tools/src/x-java.h b/gettext-tools/src/x-java.h index 82a089278..c46feeee4 100644 --- a/gettext-tools/src/x-java.h +++ b/gettext-tools/src/x-java.h @@ -1,5 +1,5 @@ /* xgettext Java backend. - Copyright (C) 2001-2003, 2006, 2014, 2018 Free Software Foundation, Inc. + Copyright (C) 2001-2003, 2006, 2014, 2018-2019 Free Software Foundation, Inc. Written by Tommy Johansson , 2001. This program is free software: you can redistribute it and/or modify @@ -31,8 +31,9 @@ extern "C" { { "java", "Java" }, \ #define SCANNERS_JAVA \ - { "Java", extract_java, \ - &flag_table_java, &formatstring_java, NULL }, \ + { "Java", extract_java, \ + &flag_table_java, \ + &formatstring_java, &formatstring_java_printf }, \ extern void extract_java (FILE *fp, const char *real_filename, const char *logical_filename, diff --git a/gettext-tools/src/xgettext.c b/gettext-tools/src/xgettext.c index b9cd243f5..ab1fdce46 100644 --- a/gettext-tools/src/xgettext.c +++ b/gettext-tools/src/xgettext.c @@ -1503,6 +1503,11 @@ xgettext_record_flag (const char *optionstring) name_start, name_end, argnum, value, pass); break; + case format_java_printf: + flag_context_list_table_insert (&flag_table_java, 1, + name_start, name_end, + argnum, value, pass); + break; case format_csharp: flag_context_list_table_insert (&flag_table_csharp, 0, name_start, name_end, diff --git a/gettext-tools/tests/Makefile.am b/gettext-tools/tests/Makefile.am index 77e710007..b32849483 100644 --- a/gettext-tools/tests/Makefile.am +++ b/gettext-tools/tests/Makefile.am @@ -132,6 +132,7 @@ TESTS = gettext-1 gettext-2 \ format-gcc-internal-1 format-gcc-internal-2 \ format-gfc-internal-1 format-gfc-internal-2 \ format-java-1 format-java-2 \ + format-java-printf-1 format-java-printf-2 \ format-kde-1 format-kde-2 \ format-kde-kuit-1 format-kde-kuit-2 \ format-librep-1 format-librep-2 \ diff --git a/gettext-tools/tests/format-java-printf-1 b/gettext-tools/tests/format-java-printf-1 new file mode 100755 index 000000000..37d4d0d16 --- /dev/null +++ b/gettext-tools/tests/format-java-printf-1 @@ -0,0 +1,337 @@ +#! /bin/sh +. "${srcdir=.}/init.sh"; path_prepend_ . ../src + +# Test recognition of Java printf format strings. + +cat <<\EOF > f-jp-1.data +# Valid: no argument +"abc%%" +# Valid: one general argument +"abc%b" +# Valid: one general argument +"abc%B" +# Valid: one general argument +"abc%h" +# Valid: one general argument +"abc%H" +# Valid: one general argument +"abc%s" +# Valid: one general argument +"abc%S" +# Valid: one character argument +"abc%c" +# Valid: one character argument +"abc%C" +# Valid: one integer argument +"abc%d" +# Valid: one integer argument +"abc%o" +# Valid: one integer argument +"abc%x" +# Valid: one integer argument +"abc%X" +# Valid: one floating-point argument +"abc%e" +# Valid: one floating-point argument +"abc%E" +# Valid: one floating-point argument +"abc%f" +# Valid: one floating-point argument +"abc%g" +# Valid: one floating-point argument +"abc%G" +# Valid: one floating-point argument +"abc%a" +# Valid: one floating-point argument +"abc%A" +# Valid: one argument with flags +"abc%0#g" +# Valid: one argument with width +"abc%2g" +# Invalid: variable width +"abc%*g" +# Valid: one argument with precision +"abc%.4g" +# Invalid: missing precision +"abc%.g" +# Invalid: variable precision +"abc%.*g" +# Valid: one argument with width and precision +"abc%14.4g" +# Invalid: unterminated +"abc%" +# Invalid: unterminated +"abc%t" +# Invalid: unknown format specifier +"abc%y" +# Invalid: flags after width +"abc%2#d" +# Invalid: twice precision +"abc%.4.2g" +# Valid: three arguments +"abc%d%x%x" +# Valid: a numbered argument +"abc%1$d" +# Invalid: zero +"abc%0$d" +# Valid: two-digit numbered arguments +"abc%11$def%10$dgh%9$dij%8$dkl%7$dmn%6$dop%5$dqr%4$dst%3$duv%2$dwx%1$dyz" +# Invalid: unterminated number +"abc%1" +# Invalid: flags before number +"abc%#1$g" +# Valid: three arguments, two with same number +"abc%1$4x,%2$c,%1$X" +# Invalid: argument with conflicting types +"abc%1$4x,%2$c,%1$s" +# Valid: no conflict +"abc%1$4x,%2$c,%1$d" +# Valid: mixing of numbered and unnumbered arguments +"abc%d%2$x" +# Valid: mixing of numbered and unnumbered arguments +"abc%5$d%x" +# Valid: mixing of numbered and unnumbered arguments, no type conflict +"abc%2$c%x%c%g" +# Invalid: mixing of numbered and unnumbered arguments, argument with conflicting types +"abc%2$c%x%g%c" +# Valid: numbered argument with width +"abc%2$#5g" +# Valid: numbered argument with precision +"abc%1$.9g" +# Valid: numbered argument with width and precision +"abc%3$5.2g" +# Valid: missing non-final argument +"abc%2$x%3$s" +# Valid: permutation +"abc%2$ddef%1$d" +# Valid: multiple uses of same argument +"abc%2$xdef%1$Sghi%2$x" +# Valid: reference to last argument +"abc%x% f-jp-1-$n.in +gettext(${string}); +EOF + ${XGETTEXT} -L Java -o f-jp-1-$n.po f-jp-1-$n.in || Exit 1 + test -f f-jp-1-$n.po || Exit 1 + fail= + if echo "$comment" | grep 'Valid:' > /dev/null; then + if grep java-printf-format f-jp-1-$n.po > /dev/null; then + : + else + fail=yes + fi + else + if grep java-printf-format f-jp-1-$n.po > /dev/null; then + fail=yes + else + : + fi + fi + if test -n "$fail"; then + echo "Format string recognition error:" 1>&2 + cat f-jp-1-$n.in 1>&2 + echo "Got:" 1>&2 + cat f-jp-1-$n.po 1>&2 + Exit 1 + fi + rm -f f-jp-1-$n.in f-jp-1-$n.po +done < f-jp-1.data + +Exit 0 diff --git a/gettext-tools/tests/format-java-printf-2 b/gettext-tools/tests/format-java-printf-2 new file mode 100755 index 000000000..ad0b168b2 --- /dev/null +++ b/gettext-tools/tests/format-java-printf-2 @@ -0,0 +1,286 @@ +#! /bin/sh +. "${srcdir=.}/init.sh"; path_prepend_ . ../src + +# Test checking of Java printf format strings. + +cat <<\EOF > f-jp-2.data +# Valid: %% doesn't count +msgid "abc%%def" +msgstr "xyz" +# Invalid: invalid msgstr +msgid "abc%%def" +msgstr "xyz%" +# Valid: same arguments +msgid "abc%s%xdef" +msgstr "xyz%s%x" +# Valid: same arguments, with different widths +msgid "abc%2sdef" +msgstr "xyz%3s" +# Valid: same arguments but in numbered syntax +msgid "abc%s%xdef" +msgstr "xyz%1$s%2$x" +# Valid: permutation +msgid "abc%s%x%cdef" +msgstr "xyz%3$c%2$x%1$s" +# Invalid: too few arguments +msgid "abc%2$xdef%1$s" +msgstr "xyz%1$s" +# Invalid: too few arguments +msgid "abc%sdef%x" +msgstr "xyz%s" +# Invalid: too many arguments +msgid "abc%xdef" +msgstr "xyz%xvw%c" +# Valid: same numbered arguments, with different widths +msgid "abc%2$5s%1$4s" +msgstr "xyz%2$4s%1$5s" +# Invalid: missing argument +msgid "abc%2$sdef%1$x" +msgstr "xyz%1$x" +# Invalid: missing argument +msgid "abc%1$sdef%2$x" +msgstr "xyz%2$x" +# Invalid: added argument +msgid "abc%1$xdef" +msgstr "xyz%1$xvw%2$c" +# Valid: type compatibility +msgid "abc%b" +msgstr "xyz%B" +# Valid: type compatibility +msgid "abc%h" +msgstr "xyz%H" +# Valid: type compatibility +msgid "abc%s" +msgstr "xyz%S" +# Valid: type compatibility +msgid "abc%b" +msgstr "xyz%h" +# Valid: type compatibility +msgid "abc%b" +msgstr "xyz%s" +# Valid: type compatibility +msgid "abc%h" +msgstr "xyz%s" +# Valid: type compatibility +msgid "abc%c" +msgstr "xyz%C" +# Valid: type compatibility +msgid "abc%d" +msgstr "xyz%o" +# Valid: type compatibility +msgid "abc%d" +msgstr "xyz%x" +# Valid: type compatibility +msgid "abc%d" +msgstr "xyz%X" +# Valid: type compatibility +msgid "abc%o" +msgstr "xyz%x" +# Valid: type compatibility +msgid "abc%o" +msgstr "xyz%X" +# Valid: type compatibility +msgid "abc%x" +msgstr "xyz%X" +# Valid: type compatibility +msgid "abc%e" +msgstr "xyz%E" +# Valid: type compatibility +msgid "abc%g" +msgstr "xyz%G" +# Valid: type compatibility +msgid "abc%a" +msgstr "xyz%A" +# Valid: type compatibility +msgid "abc%e" +msgstr "xyz%f" +# Valid: type compatibility +msgid "abc%e" +msgstr "xyz%g" +# Valid: type compatibility +msgid "abc%e" +msgstr "xyz%a" +# Valid: type compatibility +msgid "abc%f" +msgstr "xyz%g" +# Valid: type compatibility +msgid "abc%f" +msgstr "xyz%a" +# Valid: type compatibility +msgid "abc%g" +msgstr "xyz%a" +# Valid: type compatibility +msgid "abc%tF" +msgstr "xyz%Tz" +# Invalid: type incompatibility +msgid "abc%b" +msgstr "xyz%c" +# Invalid: type incompatibility +msgid "abc%b" +msgstr "xyz%d" +# Invalid: type incompatibility +msgid "abc%b" +msgstr "xyz%o" +# Invalid: type incompatibility +msgid "abc%b" +msgstr "xyz%x" +# Invalid: type incompatibility +msgid "abc%b" +msgstr "xyz%e" +# Invalid: type incompatibility +msgid "abc%b" +msgstr "xyz%f" +# Invalid: type incompatibility +msgid "abc%b" +msgstr "xyz%g" +# Invalid: type incompatibility +msgid "abc%b" +msgstr "xyz%a" +# Invalid: type incompatibility +msgid "abc%b" +msgstr "xyz%tF" +# Invalid: type incompatibility +msgid "abc%h" +msgstr "xyz%c" +# Invalid: type incompatibility +msgid "abc%h" +msgstr "xyz%d" +# Invalid: type incompatibility +msgid "abc%h" +msgstr "xyz%o" +# Invalid: type incompatibility +msgid "abc%h" +msgstr "xyz%x" +# Invalid: type incompatibility +msgid "abc%h" +msgstr "xyz%e" +# Invalid: type incompatibility +msgid "abc%h" +msgstr "xyz%f" +# Invalid: type incompatibility +msgid "abc%h" +msgstr "xyz%g" +# Invalid: type incompatibility +msgid "abc%h" +msgstr "xyz%a" +# Invalid: type incompatibility +msgid "abc%h" +msgstr "xyz%tF" +# Invalid: type incompatibility +msgid "abc%c" +msgstr "xyz%d" +# Invalid: type incompatibility +msgid "abc%c" +msgstr "xyz%o" +# Invalid: type incompatibility +msgid "abc%c" +msgstr "xyz%x" +# Invalid: type incompatibility +msgid "abc%c" +msgstr "xyz%e" +# Invalid: type incompatibility +msgid "abc%c" +msgstr "xyz%f" +# Invalid: type incompatibility +msgid "abc%c" +msgstr "xyz%g" +# Invalid: type incompatibility +msgid "abc%c" +msgstr "xyz%a" +# Invalid: type incompatibility +msgid "abc%c" +msgstr "xyz%tF" +# Invalid: type incompatibility +msgid "abc%d" +msgstr "xyz%e" +# Invalid: type incompatibility +msgid "abc%d" +msgstr "xyz%f" +# Invalid: type incompatibility +msgid "abc%d" +msgstr "xyz%g" +# Invalid: type incompatibility +msgid "abc%d" +msgstr "xyz%a" +# Invalid: type incompatibility +msgid "abc%d" +msgstr "xyz%tF" +# Invalid: type incompatibility +msgid "abc%o" +msgstr "xyz%e" +# Invalid: type incompatibility +msgid "abc%o" +msgstr "xyz%f" +# Invalid: type incompatibility +msgid "abc%o" +msgstr "xyz%g" +# Invalid: type incompatibility +msgid "abc%o" +msgstr "xyz%a" +# Invalid: type incompatibility +msgid "abc%o" +msgstr "xyz%tF" +# Invalid: type incompatibility +msgid "abc%x" +msgstr "xyz%e" +# Invalid: type incompatibility +msgid "abc%x" +msgstr "xyz%f" +# Invalid: type incompatibility +msgid "abc%x" +msgstr "xyz%g" +# Invalid: type incompatibility +msgid "abc%x" +msgstr "xyz%a" +# Invalid: type incompatibility +msgid "abc%x" +msgstr "xyz%tF" +# Invalid: type incompatibility +msgid "abc%e" +msgstr "xyz%tF" +# Invalid: type incompatibility +msgid "abc%f" +msgstr "xyz%tF" +# Invalid: type incompatibility +msgid "abc%g" +msgstr "xyz%tF" +# Invalid: type incompatibility +msgid "abc%a" +msgstr "xyz%tF" +EOF + +: ${MSGFMT=msgfmt} +n=0 +while read comment; do + read msgid_line + read msgstr_line + n=`expr $n + 1` + cat < f-jp-2-$n.po +#, java-printf-format +${msgid_line} +${msgstr_line} +EOF + fail= + if echo "$comment" | grep 'Valid:' > /dev/null; then + if ${MSGFMT} --check-format -o f-jp-2-$n.mo f-jp-2-$n.po; then + : + else + fail=yes + fi + else + ${MSGFMT} --check-format -o f-jp-2-$n.mo f-jp-2-$n.po 2> /dev/null + if test $? = 1; then + : + else + fail=yes + fi + fi + if test -n "$fail"; then + echo "Format string checking error:" 1>&2 + cat f-jp-2-$n.po 1>&2 + Exit 1 + fi + rm -f f-jp-2-$n.po f-jp-2-$n.mo +done < f-jp-2.data + +Exit 0 diff --git a/gettext-tools/tests/lang-java b/gettext-tools/tests/lang-java index 6cc2079e4..0b5c70ee7 100755 --- a/gettext-tools/tests/lang-java +++ b/gettext-tools/tests/lang-java @@ -52,6 +52,8 @@ cat <<\EOF >> Program.java System.out.println(MessageFormat.format(GettextResource.gettext(catalog,"{0} is replaced by {1}."), new Object[] { "FF", "EUR" })); System.out.println(MessageFormat.format(GettextResource.npgettext(catalog,"++","a piece of cake","{0,number} pieces of cake",n), new Object[] { new Integer(n) })); System.out.println(MessageFormat.format(GettextResource.pgettext(catalog,"++","{0} is replaced by {1}."), new Object[] { "FF", "EUR" })); + System.out.println(String.format(GettextResource.ngettext(catalog,"She has one child","She has %d children",n), n)); + System.out.println(String.format(GettextResource.gettext(catalog,"%d left-wing deputees and %d right-wing deputees"), 75, 64)); } } EOF @@ -89,6 +91,16 @@ msgstr[1] "" msgctxt "++" msgid "{0} is replaced by {1}." msgstr "" + +#, java-printf-format +msgid "She has one child" +msgid_plural "She has %d children" +msgstr[0] "" +msgstr[1] "" + +#, java-printf-format +msgid "%d left-wing deputees and %d right-wing deputees" +msgstr "" EOF : ${DIFF=diff} @@ -128,6 +140,16 @@ msgstr[1] "{0,number} morceaux de gateau succulent" msgctxt "++" msgid "{0} is replaced by {1}." msgstr "Le nouveau {1} remplace le vieux {0}." + +#, java-printf-format +msgid "She has one child" +msgid_plural "She has %d children" +msgstr[0] "Elle a un enfant" +msgstr[1] "Elle a %d enfants" + +#, java-printf-format +msgid "%d left-wing deputees and %d right-wing deputees" +msgstr "%2$d députés de droite et %d députés de gauche" EOF : ${MSGMERGE=msgmerge} @@ -175,6 +197,8 @@ cat <<\EOF > prog.ok EUR remplace FF. 2 morceaux de gateau succulent Le nouveau EUR remplace le vieux FF. +Elle a 2 enfants +64 députés de droite et 75 députés de gauche EOF cat <<\EOF > prog.oku «Votre commande, s'il vous plait», dit le garçon. @@ -182,6 +206,8 @@ cat <<\EOF > prog.oku EUR remplace FF. 2 morceaux de gateau succulent Le nouveau EUR remplace le vieux FF. +Elle a 2 enfants +64 députés de droite et 75 députés de gauche EOF : ${LOCALE_FR=fr_FR} diff --git a/gettext-tools/tests/xgettext-6 b/gettext-tools/tests/xgettext-6 index 2bb6c762f..416fe7b1c 100755 --- a/gettext-tools/tests/xgettext-6 +++ b/gettext-tools/tests/xgettext-6 @@ -37,7 +37,11 @@ EOF cat <<\EOF > xg-test6.java MessageFormat.format(gettext("java-format positive1"), gettext("java-format negative1")); -System.err.println(gettext("java-format negative2")); +String.format(gettext("java-printf-format positive1"), + gettext("java-printf-format negative1")); +System.err.printf(gettext("java-printf-format positive2"), + gettext("java-printf-format negative2")); +System.err.println(gettext("both java-format, java-printf-format negative3")); EOF cat <<\EOF > xg-test6.awk @@ -149,7 +153,21 @@ msgstr "" msgid "java-format negative1" msgstr "" -msgid "java-format negative2" +#, java-printf-format +msgid "java-printf-format positive1" +msgstr "" + +msgid "java-printf-format negative1" +msgstr "" + +#, java-printf-format +msgid "java-printf-format positive2" +msgstr "" + +msgid "java-printf-format negative2" +msgstr "" + +msgid "both java-format, java-printf-format negative3" msgstr "" #, awk-format diff --git a/gettext-tools/woe32dll/gettextsrc-exports.c b/gettext-tools/woe32dll/gettextsrc-exports.c index 8de53a7b0..4477ae8d4 100644 --- a/gettext-tools/woe32dll/gettextsrc-exports.c +++ b/gettext-tools/woe32dll/gettextsrc-exports.c @@ -33,6 +33,7 @@ VARIABLE(formatstring_elisp) VARIABLE(formatstring_gcc_internal) VARIABLE(formatstring_gfc_internal) VARIABLE(formatstring_java) +VARIABLE(formatstring_java_printf) VARIABLE(formatstring_javascript) VARIABLE(formatstring_kde) VARIABLE(formatstring_kde_kuit)