* gettext-tools/src/x-ruby.h: New file.
* gettext-tools/src/x-ruby.c: New file.
* gettext-tools/src/xgettext.h (verbose): New declaration.
* gettext-tools/src/xgettext.c: Include x-ruby.h.
(verbose): New declaration.
(flag_table_ruby): New variable.
(long_options): Add '--verbose'.
(main): Update for Ruby. Handle '-v'/'--verbose' option.
(usage): Document the '-L Ruby' and '-v' options.
(xgettext_record_flag, language_to_extractor, extension_to_language): Update for
Ruby.
* gettext-tools/src/Makefile.am (noinst_HEADERS): Add x-ruby.h.
(xgettext_SOURCES): Add x-ruby.c.
* gettext-tools/src/FILES: Mention x-ruby.h, x-ruby.c.
* gettext-tools/tests/xgettext-ruby-1: New file.
* gettext-tools/tests/format-ruby-1: New file.
* gettext-tools/tests/format-ruby-2: New file.
* gettext-tools/tests/Makefile.am (TESTS): Add them.
* gettext-tools/doc/gettext.texi (Ruby): New section.
* gettext-tools/doc/xgettext.texi: Document the '-L Ruby' and '-v' options.
* HACKING: Document the recommended Ruby packages.
* NEWS: Mention the Ruby support.
+ Homepage: http://www.php.net/
+ Ubuntu package: php
+ * Ruby
+ + Homepage: https://www.ruby-lang.org/en/
+ + Ubuntu package: ruby
+ * The ruby-gettext package
+ + Homepage: https://ruby-gettext.github.io/
+ + Ubuntu package: ruby-gettext
+
* lua
+ Homepage: https://www.lua.org/
+ Ubuntu package: lua5.2 or lua5.1
o xgettext now recognizes text blocks as string literals.
- JavaScript:
xgettext parses JSX expressions more reliably.
+ - Ruby:
+ o xgettext now supports Ruby.
+ o 'msgfmt -c' now verifies the syntax of translations of Ruby format
+ strings.
* Runtime behaviour:
- On native Windows platforms, the directory that contains the message
It also serves as a reference for the free Translation Project.
@copying
-Copyright (C) 1995-1998, 2001-2019 Free Software Foundation, Inc.
+Copyright (C) 1995-1998, 2001-2020 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
@page
@vskip 0pt plus 1filll
@c @insertcopying
-Copyright (C) 1995-1998, 2001-2019 Free Software Foundation, Inc.
+Copyright (C) 1995-1998, 2001-2020 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
* Tcl:: Tcl - Tk's scripting language
* Perl:: Perl
* PHP:: PHP Hypertext Preprocessor
+* Ruby:: Ruby
* Pike:: Pike
* GCC-source:: GNU Compiler Collection sources
* Lua:: Lua
* Tcl:: Tcl - Tk's scripting language
* Perl:: Perl
* PHP:: PHP Hypertext Preprocessor
+* Ruby:: Ruby
* Pike:: Pike
* GCC-source:: GNU Compiler Collection sources
* Lua:: Lua
An example is available in the @file{examples} directory: @code{hello-php}.
+@node Ruby
+@subsection Ruby
+@cindex Ruby
+
+@table @asis
+@item RPMs
+ruby, ruby-gettext
+
+@item Ubuntu packages
+ruby, ruby-gettext
+
+@item File extension
+@code{rb}
+
+@item String syntax
+@code{"abc"}, @code{'abc'}, @code{%q/abc/} etc.,
+@code{%q(abc)}, @code{%q[abc]}, @code{%q@{abc@}}
+
+@item gettext shorthand
+@code{_("abc")}
+
+@item gettext/ngettext functions
+@code{gettext}, @code{ngettext}
+
+@item textdomain
+---
+
+@item bindtextdomain
+@code{bindtextdomain} function
+
+@item setlocale
+---
+
+@item Prerequisite
+@code{require 'gettext'}
+@code{include GetText}
+
+@item Use or emulate GNU gettext
+emulate
+
+@item Extractor
+@code{xgettext}
+
+@item Formatting with positions
+@code{sprintf("%2$d %1$d", x, y)}
+@*@code{"%@{new@} replaces %@{old@}" % @{:old => oldvalue, :new => newvalue@}}
+
+@item Portability
+fully portable
+
+@item po-mode marking
+---
+@end table
+
+@c An example is available in the @file{examples} directory: @code{hello-ruby}.
+
@node Pike
@subsection Pike
@cindex Pike
@c This file is part of the GNU gettext manual.
-@c Copyright (C) 1995-2019 Free Software Foundation, Inc.
+@c Copyright (C) 1995-2020 Free Software Foundation, Inc.
@c See the file gettext.texi for copying conditions.
@pindex xgettext
are @code{C}, @code{C++}, @code{ObjectiveC}, @code{PO}, @code{Shell},
@code{Python}, @code{Lisp}, @code{EmacsLisp}, @code{librep}, @code{Scheme},
@code{Smalltalk}, @code{Java}, @code{JavaProperties}, @code{C#}, @code{awk},
-@code{YCP}, @code{Tcl}, @code{Perl}, @code{PHP}, @code{GCC-source},
-@code{NXStringTable}, @code{RST}, @code{RSJ}, @code{Glade}, @code{Lua},
-@code{JavaScript}, @code{Vala}, @code{GSettings}, @code{Desktop}.
+@code{YCP}, @code{Tcl}, @code{Perl}, @code{PHP}, @code{Ruby},
+@code{GCC-source}, @code{NXStringTable}, @code{RST}, @code{RSJ}, @code{Glade},
+@code{Lua}, @code{JavaScript}, @code{Vala}, @code{GSettings}, @code{Desktop}.
@item -C
@itemx --c++
@opindex --version@r{, @code{xgettext} option}
Output version information and exit.
+@item -v
+@itemx --verbose
+@opindex -v@r{, @code{xgettext} option}
+@opindex --verbose@r{, @code{xgettext} option}
+Increase verbosity level.
+
@end table
| x-php.h
| x-php.c
| String extractor for PHP.
+| x-ruby.h
+| x-ruby.c
+| String extractor for Ruby.
| x-rst.h
| x-rst.c
| String extractor from .rst files, for Object Pascal.
xg-arglist-callshape.h xg-arglist-parser.h xg-message.h \
x-c.h x-po.h x-sh.h x-python.h x-lisp.h x-elisp.h x-librep.h \
x-scheme.h x-smalltalk.h x-java.h x-properties.h x-csharp.h x-awk.h x-ycp.h \
-x-tcl.h x-perl.h x-php.h x-stringtable.h x-rst.h x-glade.h x-lua.h \
+x-tcl.h x-perl.h x-php.h x-ruby.h x-stringtable.h x-rst.h x-glade.h x-lua.h \
x-javascript.h x-vala.h x-gsettings.h x-desktop.h x-appdata.h
EXTRA_DIST += FILES project-id
x-tcl.c \
x-perl.c \
x-php.c \
+ x-ruby.c \
x-rst.c \
x-lua.c \
x-javascript.c \
--- /dev/null
+/* xgettext Ruby backend.
+ Copyright (C) 2020 Free Software Foundation, Inc.
+ Written by Bruno Haible <bruno@clisp.org>, 2020.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+/* Specification. */
+#include "x-ruby.h"
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "message.h"
+#include "sh-quote.h"
+#include "spawn-pipe.h"
+#include "wait-process.h"
+#include "xvasprintf.h"
+#include "x-po.h"
+#include "xgettext.h"
+#include "xg-message.h"
+#include "c-strstr.h"
+#include "read-catalog-abstract.h"
+#include "error.h"
+#include "gettext.h"
+
+/* A convenience macro. I don't like writing gettext() every time. */
+#define _(str) gettext (str)
+
+/* The Ruby syntax is defined in
+ https://ruby-doc.org/core-2.7.1/doc/syntax_rdoc.html
+ https://ruby-doc.org/core-2.7.1/doc/syntax/comments_rdoc.html
+ https://ruby-doc.org/core-2.7.1/doc/syntax/literals_rdoc.html
+ We don't parse Ruby directly, but instead rely on the 'rxgettext' program
+ from https://github.com/ruby-gettext/gettext . */
+
+
+/* ====================== Keyword set customization. ====================== */
+
+/* This function currently has no effect. */
+void
+x_ruby_extract_all (void)
+{
+}
+
+/* This function currently has no effect. */
+void
+x_ruby_keyword (const char *keyword)
+{
+}
+
+/* This function currently has no effect. */
+void
+init_flag_table_ruby (void)
+{
+}
+
+
+/* ========================= Extracting strings. ========================== */
+
+void
+extract_ruby (const char *real_filename, const char *logical_filename,
+ flag_context_list_table_ty *flag_table,
+ msgdomain_list_ty *mdlp)
+{
+ const char *progname = "rxgettext";
+ char *dummy_filename;
+ msgdomain_list_ty *mdlp2;
+ int pass;
+
+ dummy_filename = xasprintf (_("(output from '%s')"), progname);
+
+ /* Invoke rgettext twice:
+ 1. to get the messages, without ruby-format flags.
+ 2. to get the 'xgettext:' comments that guide us while adding
+ [no-]ruby-format flags. */
+ mdlp2 = msgdomain_list_alloc (true);
+ for (pass = 0; pass < 2; pass++)
+ {
+ char *argv[4];
+ unsigned int i;
+ pid_t child;
+ int fd[1];
+ FILE *fp;
+ int exitstatus;
+
+ /* Prepare arguments. */
+ argv[0] = (char *) progname;
+ i = 1;
+
+ if (pass > 0)
+ argv[i++] = (char *) "--add-comments=xgettext:";
+ else
+ {
+ if (add_all_comments)
+ argv[i++] = (char *) "--add-comments";
+ else if (comment_tag != NULL)
+ argv[i++] = xasprintf ("--add-comments=%s", comment_tag);
+ }
+
+ argv[i++] = (char *) real_filename;
+
+ argv[i] = NULL;
+
+ if (verbose)
+ {
+ char *command = shell_quote_argv (argv);
+ error (0, 0, "%s", command);
+ free (command);
+ }
+
+ child = create_pipe_in (progname, progname, argv,
+ DEV_NULL, false, true, true, fd);
+
+ fp = fdopen (fd[0], "r");
+ if (fp == NULL)
+ error (EXIT_FAILURE, errno, _("fdopen() failed"));
+
+ /* Read the resulting PO file. */
+ extract_po (fp, dummy_filename, dummy_filename, flag_table,
+ pass == 0 ? mdlp : mdlp2);
+
+ fclose (fp);
+
+ /* Remove zombie process from process list, and retrieve exit status. */
+ exitstatus =
+ wait_subprocess (child, progname, false, false, true, true, NULL);
+ if (exitstatus != 0)
+ error (EXIT_FAILURE, 0, _("%s subprocess failed with exit code %d"),
+ progname, exitstatus);
+ }
+
+ /* Add [no-]ruby-format flags and process 'xgettext:' comments.
+ This processing is similar to the one done in remember_a_message(). */
+ if (mdlp->nitems == 1 && mdlp2->nitems == 1)
+ {
+ message_list_ty *mlp = mdlp->item[0]->messages;
+ message_list_ty *mlp2 = mdlp2->item[0]->messages;
+ size_t j;
+
+ for (j = 0; j < mlp->nitems; j++)
+ {
+ message_ty *mp = mlp->item[j];
+
+ if (!is_header (mp))
+ {
+ /* Find 'xgettext:' comments and apply them to mp. */
+ message_ty *mp2 =
+ message_list_search (mlp2, mp->msgctxt, mp->msgid);
+
+ if (mp2 != NULL && mp2->comment_dot != NULL)
+ {
+ string_list_ty *mp2_comment_dot = mp2->comment_dot;
+ size_t k;
+
+ for (k = 0; k < mp2_comment_dot->nitems; k++)
+ {
+ const char *s = mp2_comment_dot->item[k];
+
+ /* To reduce the possibility of unwanted matches we do a
+ two step match: the line must contain 'xgettext:' and
+ one of the possible format description strings. */
+ const char *t = c_strstr (s, "xgettext:");
+ if (t != NULL)
+ {
+ bool tmp_fuzzy;
+ enum is_format tmp_format[NFORMATS];
+ struct argument_range tmp_range;
+ enum is_wrap tmp_wrap;
+ enum is_syntax_check tmp_syntax_check[NSYNTAXCHECKS];
+ bool interesting;
+ size_t i;
+
+ t += strlen ("xgettext:");
+
+ po_parse_comment_special (t, &tmp_fuzzy, tmp_format,
+ &tmp_range, &tmp_wrap,
+ tmp_syntax_check);
+
+ interesting = false;
+ for (i = 0; i < NFORMATS; i++)
+ if (tmp_format[i] != undecided)
+ {
+ mp->is_format[i] = tmp_format[i];
+ interesting = true;
+ }
+ if (has_range_p (tmp_range))
+ {
+ intersect_range (mp, &tmp_range);
+ interesting = true;
+ }
+ if (tmp_wrap != undecided)
+ {
+ mp->do_wrap = tmp_wrap;
+ interesting = true;
+ }
+ for (i = 0; i < NSYNTAXCHECKS; i++)
+ if (tmp_syntax_check[i] != undecided)
+ {
+ mp->do_syntax_check[i] = tmp_syntax_check[i];
+ interesting = true;
+ }
+
+ /* If the "xgettext:" marker was followed by an
+ interesting keyword, and we updated our
+ is_format/do_wrap variables, eliminate the comment
+ from the #. comments. */
+ if (interesting)
+ if (mp->comment_dot != NULL)
+ {
+ const char *removed =
+ string_list_remove (mp->comment_dot, s);
+
+ if (removed != NULL)
+ free ((char *) removed);
+ }
+ }
+ }
+ }
+
+ /* Now evaluate the consequences of the 'xgettext:' comments, */
+ decide_is_format (mp);
+ decide_do_wrap (mp);
+ decide_syntax_check (mp);
+ }
+ }
+ }
+
+ msgdomain_list_free (mdlp2);
+
+ free (dummy_filename);
+}
--- /dev/null
+/* xgettext Ruby backend.
+ Copyright (C) 2020 Free Software Foundation, Inc.
+ Written by Bruno Haible <bruno@clisp.org>, 2020.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+
+#include <stdio.h>
+
+#include "message.h"
+#include "xg-arglist-context.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define EXTENSIONS_RUBY \
+ { "rb", "Ruby" }, \
+
+#define SCANNERS_RUBY \
+ { "Ruby", NULL, extract_ruby, \
+ &flag_table_ruby, &formatstring_ruby, NULL }, \
+
+extern void extract_ruby (const char *real_filename,
+ const char *logical_filename,
+ flag_context_list_table_ty *flag_table,
+ msgdomain_list_ty *mdlp);
+
+extern void x_ruby_keyword (const char *keyword);
+extern void x_ruby_extract_all (void);
+
+extern void init_flag_table_ruby (void);
+
+
+#ifdef __cplusplus
+}
+#endif
#include "x-tcl.h"
#include "x-perl.h"
#include "x-php.h"
+#include "x-ruby.h"
#include "x-stringtable.h"
#include "x-rst.h"
#include "x-glade.h"
/* If nonzero omit header with information about this run. */
int xgettext_omit_header;
+/* Be more verbose. */
+int verbose = 0;
+
/* Table of flag_context_list_ty tables. */
static flag_context_list_table_ty flag_table_c;
static flag_context_list_table_ty flag_table_cxx_qt;
static flag_context_list_table_ty flag_table_tcl;
static flag_context_list_table_ty flag_table_perl;
static flag_context_list_table_ty flag_table_php;
+static flag_context_list_table_ty flag_table_ruby;
static flag_context_list_table_ty flag_table_lua;
static flag_context_list_table_ty flag_table_javascript;
static flag_context_list_table_ty flag_table_vala;
" <its:translateRule selector=\"/*\" translate=\"no\"/>" \
"</its:rules>"
-/* If nonzero add comments used by itstool. */
+/* If nonzero add comments used by itstool. */
static bool add_itstool_comments = false;
/* Long options. */
{ "stringtable-output", no_argument, NULL, CHAR_MAX + 7 },
{ "style", required_argument, NULL, CHAR_MAX + 15 },
{ "trigraphs", no_argument, NULL, 'T' },
+ { "verbose", no_argument, NULL, 'v' },
{ "version", no_argument, NULL, 'V' },
{ "width", required_argument, NULL, 'w' },
{ NULL, 0, NULL, 0 }
init_flag_table_tcl ();
init_flag_table_perl ();
init_flag_table_php ();
+ init_flag_table_ruby ();
init_flag_table_lua ();
init_flag_table_javascript ();
init_flag_table_vala ();
while ((optchar = getopt_long (argc, argv,
- "ac::Cd:D:eEf:Fhijk::l:L:m::M::no:p:sTVw:W:x:",
+ "ac::Cd:D:eEf:Fhijk::l:L:m::M::no:p:sTvVw:W:x:",
long_options, NULL)) != EOF)
switch (optchar)
{
x_tcl_extract_all ();
x_perl_extract_all ();
x_php_extract_all ();
+ x_ruby_extract_all ();
x_lua_extract_all ();
x_javascript_extract_all ();
x_vala_extract_all ();
x_tcl_keyword (optarg);
x_perl_keyword (optarg);
x_php_keyword (optarg);
+ x_ruby_keyword (optarg);
x_lua_keyword (optarg);
x_javascript_keyword (optarg);
x_vala_keyword (optarg);
x_c_trigraphs ();
break;
+ case 'v':
+ verbose++;
+ break;
+
case 'V':
do_version = true;
break;
(C, C++, ObjectiveC, PO, Shell, Python, Lisp,\n\
EmacsLisp, librep, Scheme, Smalltalk, Java,\n\
JavaProperties, C#, awk, YCP, Tcl, Perl, PHP,\n\
- GCC-source, NXStringTable, RST, RSJ, Glade,\n\
- Lua, JavaScript, Vala, Desktop)\n"));
+ Ruby, GCC-source, NXStringTable, RST, RSJ,\n\
+ Glade, Lua, JavaScript, Vala, Desktop)\n"));
printf (_("\
-C, --c++ shorthand for --language=C++\n"));
printf (_("\
-h, --help display this help and exit\n"));
printf (_("\
-V, --version output version information and exit\n"));
+ printf (_("\
+ -v, --verbose increase verbosity level\n"));
printf ("\n");
/* TRANSLATORS: The first placeholder is the web address of the Savannah
project of this package. The second placeholder is the bug-reporting
name_start, name_end,
argnum, value, pass);
break;
+ case format_ruby:
+ flag_context_list_table_insert (&flag_table_ruby, 0,
+ name_start, name_end,
+ argnum, value, pass);
+ break;
case format_gcc_internal:
flag_context_list_table_insert (&flag_table_gcc_internal, 0,
name_start, name_end,
SCANNERS_TCL
SCANNERS_PERL
SCANNERS_PHP
+ SCANNERS_RUBY
SCANNERS_STRINGTABLE
SCANNERS_RST
SCANNERS_GLADE
EXTENSIONS_TCL
EXTENSIONS_PERL
EXTENSIONS_PHP
+ EXTENSIONS_RUBY
EXTENSIONS_STRINGTABLE
EXTENSIONS_RST
EXTENSIONS_GLADE
/* xgettext common functions.
- Copyright (C) 2001-2003, 2005-2006, 2008-2009, 2011, 2013-2014, 2018 Free Software Foundation, Inc.
+ Copyright (C) 2001-2003, 2005-2006, 2008-2009, 2011, 2013-2014, 2018, 2020 Free Software Foundation, Inc.
Written by Peter Miller <millerp@canb.auug.org.au>
and Bruno Haible <haible@clisp.cons.org>, 2001.
If false, keep the header entry present in the input. */
extern int xgettext_omit_header;
+/* Be more verbose. */
+extern int verbose;
+
extern enum is_syntax_check default_syntax_check[NSYNTAXCHECKS];
/* Language dependent format string parser.
xgettext-rst-1 xgettext-rst-2 \
xgettext-python-1 xgettext-python-2 xgettext-python-3 \
xgettext-python-4 \
+ xgettext-ruby-1 \
xgettext-scheme-1 xgettext-scheme-2 xgettext-scheme-3 \
xgettext-scheme-4 \
xgettext-sh-1 xgettext-sh-2 xgettext-sh-3 xgettext-sh-4 xgettext-sh-5 \
format-perl-mixed-1 format-perl-mixed-2 \
format-qt-1 format-qt-2 \
format-qt-plural-1 format-qt-plural-2 \
+ format-ruby-1 format-ruby-2 \
format-scheme-1 format-scheme-2 \
format-sh-1 format-sh-2 \
format-tcl-1 format-tcl-2 \
--- /dev/null
+#! /bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test recognition of Ruby format strings.
+
+(rxgettext --version) >/dev/null 2>/dev/null \
+ || { echo "Skipping test: rxgettext not found"; Exit 77; }
+
+cat <<\EOF > f-r-1.data
+# Valid: no argument
+"abc%%"
+# Valid: one string argument (unnumbered)
+"abc%s"
+# Valid: one string argument (numbered)
+"abc%1$s"
+# Valid: one string argument (named)
+"abc%<foo>s"
+# Valid: one string argument (named)
+"abc%{foo}"
+# Valid: one escaped string argument (unnumbered)
+"abc%p"
+# Valid: one escaped string argument (numbered)
+"abc%1$p"
+# Valid: one escaped string argument (named)
+"abc%<foo>p"
+# Valid: one character argument (unnumbered)
+"abc%c"
+# Valid: one character argument (numbered)
+"abc%1$c"
+# Valid: one character argument (named)
+"abc%<foo>c"
+# Valid: one integer argument (unnumbered)
+"abc%d"
+# Valid: one integer argument (numbered)
+"abc%1$d"
+# Valid: one integer argument (named)
+"abc%<foo>d"
+# Valid: one integer argument (unnumbered)
+"abc%i"
+# Valid: one integer argument (numbered)
+"abc%1$i"
+# Valid: one integer argument (named)
+"abc%<foo>i"
+# Valid: one integer argument (unnumbered)
+"abc%u"
+# Valid: one integer argument (numbered)
+"abc%1$u"
+# Valid: one integer argument (named)
+"abc%<foo>u"
+# Valid: one integer argument (unnumbered)
+"abc%o"
+# Valid: one integer argument (numbered)
+"abc%1$o"
+# Valid: one integer argument (named)
+"abc%<foo>o"
+# Valid: one integer argument (unnumbered)
+"abc%x"
+# Valid: one integer argument (numbered)
+"abc%1$x"
+# Valid: one integer argument (named)
+"abc%<foo>x"
+# Valid: one integer argument (unnumbered)
+"abc%X"
+# Valid: one integer argument (numbered)
+"abc%1$X"
+# Valid: one integer argument (named)
+"abc%<foo>X"
+# Valid: one integer argument (unnumbered)
+"abc%b"
+# Valid: one integer argument (numbered)
+"abc%1$b"
+# Valid: one integer argument (named)
+"abc%<foo>b"
+# Valid: one integer argument (unnumbered)
+"abc%B"
+# Valid: one integer argument (numbered)
+"abc%1$B"
+# Valid: one integer argument (named)
+"abc%<foo>B"
+# Valid: one floating-point argument (unnumbered)
+"abc%f"
+# Valid: one floating-point argument (numbered)
+"abc%1$f"
+# Valid: one floating-point argument (named)
+"abc%<foo>f"
+# Valid: one floating-point argument (unnumbered)
+"abc%g"
+# Valid: one floating-point argument (numbered)
+"abc%1$g"
+# Valid: one floating-point argument (named)
+"abc%<foo>g"
+# Valid: one floating-point argument (unnumbered)
+"abc%G"
+# Valid: one floating-point argument (numbered)
+"abc%1$G"
+# Valid: one floating-point argument (named)
+"abc%<foo>G"
+# Valid: one floating-point argument (unnumbered)
+"abc%e"
+# Valid: one floating-point argument (numbered)
+"abc%1$e"
+# Valid: one floating-point argument (named)
+"abc%<foo>e"
+# Valid: one floating-point argument (unnumbered)
+"abc%E"
+# Valid: one floating-point argument (numbered)
+"abc%1$E"
+# Valid: one floating-point argument (named)
+"abc%<foo>E"
+# Valid: one floating-point argument (unnumbered)
+"abc%a"
+# Valid: one floating-point argument (numbered)
+"abc%1$a"
+# Valid: one floating-point argument (named)
+"abc%<foo>a"
+# Valid: one floating-point argument (unnumbered)
+"abc%A"
+# Valid: one floating-point argument (numbered)
+"abc%1$A"
+# Valid: one floating-point argument (named)
+"abc%<foo>A"
+# Valid: one argument with flags (unnumbered)
+"abc%0#g"
+# Valid: one argument with flags (numbered)
+"abc%1$0#g"
+# Valid: one argument with flags (numbered)
+"abc%0#1$g"
+# Valid: one argument with flags (named)
+"abc%<foo>0#g"
+# Valid: one argument with flags (named)
+"abc%0#<foo>g"
+# Valid: one argument with width (unnumbered)
+"abc%2g"
+# Valid: one argument with width (numbered)
+"abc%1$2g"
+# Valid: one argument with width (named)
+"abc%<foo>2g"
+# Valid: one argument with width (named)
+"abc%2<foo>g"
+# Valid: one argument with width (unnumbered)
+"abc%*g"
+# Valid: one argument with width (numbered)
+"abc%2$*1$g"
+# Valid: one argument with precision (unnumbered)
+"abc%.4g"
+# Valid: one argument with precision (numbered)
+"abc%1$.4g"
+# Valid: one argument with precision (named)
+"abc%<foo>.4g"
+# Valid: one argument with precision (named)
+"abc%.4<foo>g"
+# Valid: one argument with precision (unnumbered)
+"abc%.*g"
+# Valid: one argument with precision (numbered)
+"abc%2$.*1$g"
+# Valid: one argument with width and precision (unnumbered)
+"abc%14.4g"
+# Valid: one argument with width and precision (numbered)
+"abc%1$14.4g"
+# Valid: one argument with width and precision (named)
+"abc%<foo>14.4g"
+# Valid: one argument with width and precision (named)
+"abc%14<foo>.4g"
+# Valid: one argument with width and precision (named)
+"abc%14.4<foo>g"
+# Valid: one argument with width and precision (unnumbered)
+"abc%14.*g"
+# Valid: one argument with width and precision (numbered)
+"abc%2$14.*1$g"
+# Valid: one argument with width and precision (unnumbered)
+"abc%*.4g"
+# Valid: one argument with width and precision (numbered)
+"abc%2$*1$.4g"
+# Valid: one argument with width and precision (unnumbered)
+"abc%*.*g"
+# Valid: one argument with width and precision (numbered)
+"abc%3$*1$.*2$g"
+# Invalid: unterminated directive
+"abc%"
+# Invalid: unterminated name
+"abc%<value"
+# Invalid: unterminated name
+"abc%{value"
+# Invalid: unterminated directive
+"abc%<value>"
+# Invalid: unknown format specifier (unnumbered)
+"abc%y"
+# Invalid: mixing unnumbered and numbered in the same directive
+"abc%2$*g"
+# Invalid: mixing unnumbered and numbered in the same directive
+"abc%*1$g"
+# Invalid: mixing unnumbered and numbered in the same directive
+"abc%2$.*g"
+# Invalid: mixing unnumbered and numbered in the same directive
+"abc%.*1$g"
+# Invalid: mixing unnumbered and numbered in different directives
+"abc%d%2$g"
+# Invalid: mixing unnumbered and numbered in different directives
+"abc%1$d%g"
+# Invalid: mixing unnumbered and named in the same directive
+"abc%*<foo>g"
+# Invalid: mixing unnumbered and named in the same directive
+"abc%.*<foo>g"
+# Invalid: mixing unnumbered and named in different directives
+"abc%d%<foo>g"
+# Invalid: mixing unnumbered and named in different directives
+"abc%<foo>d%g"
+# Invalid: mixing numbered and named in the same directive
+"abc%*1$<foo>g"
+# Invalid: mixing numbered and named in the same directive
+"abc%.*1$<foo>g"
+# Invalid: mixing numbered and named in different directives
+"abc%2$d%<foo>g"
+# Invalid: mixing numbered and named in different directives
+"abc%<foo>d%2$g"
+# Invalid: flags after width (unnumbered)
+"abc%*0g"
+# Invalid: flags after width (numbered)
+"abc%2$*1$0g"
+# Invalid: flags after precision (unnumbered)
+"abc%.*0g"
+# Invalid: flags after precision (numbered)
+"abc%2$.*1$0g"
+# Invalid: width after precision (unnumbered)
+"abc%.*14g"
+# Invalid: width after precision (unnumbered)
+"abc%.4*g"
+# Invalid: width after precision (unnumbered)
+"abc%.**g"
+# Invalid: width after precision (numbered)
+"abc%2$.*1$14g"
+# Invalid: width after precision (numbered)
+"abc%2$.4*1$g"
+# Invalid: width after precision (numbered)
+"abc%3$.*1$*2$g"
+# Invalid: twice width (unnumbered)
+"abc%2*g"
+# Invalid: twice width (unnumbered)
+"abc%*2g"
+# Invalid: twice width (numbered)
+"abc%2$2*1$g"
+# Invalid: twice width (numbered)
+"abc%2$*1$2g"
+# Invalid: twice precision (unnumbered)
+"abc%.4.2g"
+# Invalid: twice precision (numbered)
+"abc%1$.4.2g"
+# Valid: three arguments
+"abc%d%u%u"
+# Valid: an unused argument
+"abc%2$d%3$u"
+# Valid: a named argument
+"abc%<value>d"
+# Valid: a named argument
+"abc%{value}"
+# Valid: an empty name
+"abc%<>d"
+# Valid: an empty name
+"abc%{}"
+# Valid: ignored named argument
+"abc%<dummy>%"
+# Valid: three arguments, two with equal names
+"abc%<addr>4x,%<char>c,%<addr>u"
+# Invalid: argument with conflicting types
+"abc%<addr>4x,%<char>c,%<addr>s"
+# Valid: no conflict
+"abc%<addr>s,%{addr}"
+EOF
+
+: ${XGETTEXT=xgettext}
+n=0
+while read comment; do
+ read string
+ n=`expr $n + 1`
+ cat <<EOF > f-r-1-$n.in
+gettext(${string});
+EOF
+ ${XGETTEXT} -L Ruby -o f-r-1-$n.po f-r-1-$n.in || Exit 1
+ test -f f-r-1-$n.po || Exit 1
+ fail=
+ if echo "$comment" | grep 'Valid:' > /dev/null; then
+ if grep ruby-format f-r-1-$n.po > /dev/null; then
+ :
+ else
+ fail=yes
+ fi
+ else
+ if grep ruby-format f-r-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-r-1-$n.in 1>&2
+ echo "Got:" 1>&2
+ cat f-r-1-$n.po 1>&2
+ Exit 1
+ fi
+ rm -f f-r-1-$n.in f-r-1-$n.po
+done < f-r-1.data
+
+Exit 0
--- /dev/null
+#! /bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test checking of Ruby format strings.
+
+cat <<\EOF > f-r-2.data
+# Valid: %% doesn't count
+msgid "abc%%def"
+msgstr "xyz"
+# Invalid: invalid msgstr
+msgid "abc%%def"
+msgstr "xyz%"
+# Valid: same arguments, with different widths (argument list)
+msgid "abc%2sdef"
+msgstr "xyz%3s"
+# Valid: same arguments, with different widths (argument list)
+msgid "abc%2sdef"
+msgstr "xyz%1$3s"
+# Valid: same arguments, with different widths (named)
+msgid "abc%<foo>2sdef"
+msgstr "xyz%<foo>3s"
+# Valid: same arguments, with different widths (named)
+msgid "abc%<date>5s%<time>4s"
+msgstr "xyz%<date>4s%<time>5s"
+# Invalid: too few arguments (argument list)
+msgid "abc%sdef%u"
+msgstr "xyz%s"
+# Invalid: too few arguments (named)
+msgid "abc%<foo>sdef%<bar>u"
+msgstr "xyz%<foo>s"
+# Invalid: too many arguments (argument list)
+msgid "abc%udef"
+msgstr "xyz%uvw%c"
+# Invalid: too many arguments (named)
+msgid "abc%<foo>udef"
+msgstr "xyz%<foo>uvw%<bar>c"
+# Valid: permutation (argument list)
+msgid "abc%3$d%1$c%2$sdef"
+msgstr "xyz%2$s%1$c%3$d"
+# Valid: permutation (named)
+msgid "abc%<3>d%<1>c%<2>sdef"
+msgstr "xyz%<2>s%<1>c%<3>d"
+# Invalid: missing argument (named)
+msgid "abc%<2>sdef%<1>u"
+msgstr "xyz%<1>u"
+# Invalid: missing argument (named)
+msgid "abc%<1>sdef%<2>u"
+msgstr "xyz%<2>u"
+# Invalid: added argument (named)
+msgid "abc%<foo>udef"
+msgstr "xyz%<foo>uvw%<char>c"
+# Invalid: added argument (named)
+msgid "abc%<foo>udef"
+msgstr "xyz%<zoo>cvw%<foo>u"
+# Invalid: unnamed vs. named arguments
+msgid "abc%sdef"
+msgstr "xyz%<value>s"
+# Invalid: named vs. unnamed arguments
+msgid "abc%{value}def"
+msgstr "xyz%s"
+# Valid: unnumbered vs. numbered arguments
+msgid "abc%sdef%d"
+msgstr "xyz%2$duvw%1$s"
+# Valid: numbered vs. unnumbered arguments
+msgid "abc%1$sdef%2$d"
+msgstr "xyz%suvw%d"
+# Valid: type compatibility (argument list)
+msgid "abc%d"
+msgstr "xyz%i"
+# Valid: type compatibility (argument list)
+msgid "abc%d"
+msgstr "xyz%u"
+# Valid: type compatibility (argument list)
+msgid "abc%d"
+msgstr "xyz%o"
+# Valid: type compatibility (argument list)
+msgid "abc%d"
+msgstr "xyz%x"
+# Valid: type compatibility (argument list)
+msgid "abc%d"
+msgstr "xyz%X"
+# Valid: type compatibility (argument list)
+msgid "abc%d"
+msgstr "xyz%b"
+# Valid: type compatibility (argument list)
+msgid "abc%d"
+msgstr "xyz%B"
+# Valid: type compatibility (argument list)
+msgid "abc%i"
+msgstr "xyz%u"
+# Valid: type compatibility (argument list)
+msgid "abc%i"
+msgstr "xyz%o"
+# Valid: type compatibility (argument list)
+msgid "abc%i"
+msgstr "xyz%x"
+# Valid: type compatibility (argument list)
+msgid "abc%i"
+msgstr "xyz%X"
+# Valid: type compatibility (argument list)
+msgid "abc%i"
+msgstr "xyz%b"
+# Valid: type compatibility (argument list)
+msgid "abc%i"
+msgstr "xyz%B"
+# Valid: type compatibility (argument list)
+msgid "abc%u"
+msgstr "xyz%o"
+# Valid: type compatibility (argument list)
+msgid "abc%u"
+msgstr "xyz%x"
+# Valid: type compatibility (argument list)
+msgid "abc%u"
+msgstr "xyz%X"
+# Valid: type compatibility (argument list)
+msgid "abc%u"
+msgstr "xyz%b"
+# Valid: type compatibility (argument list)
+msgid "abc%u"
+msgstr "xyz%B"
+# Valid: type compatibility (argument list)
+msgid "abc%o"
+msgstr "xyz%x"
+# Valid: type compatibility (argument list)
+msgid "abc%o"
+msgstr "xyz%X"
+# Valid: type compatibility (argument list)
+msgid "abc%o"
+msgstr "xyz%b"
+# Valid: type compatibility (argument list)
+msgid "abc%o"
+msgstr "xyz%B"
+# Valid: type compatibility (argument list)
+msgid "abc%x"
+msgstr "xyz%X"
+# Valid: type compatibility (argument list)
+msgid "abc%x"
+msgstr "xyz%b"
+# Valid: type compatibility (argument list)
+msgid "abc%x"
+msgstr "xyz%B"
+# Valid: type compatibility (argument list)
+msgid "abc%X"
+msgstr "xyz%b"
+# Valid: type compatibility (argument list)
+msgid "abc%X"
+msgstr "xyz%B"
+# Valid: type compatibility (argument list)
+msgid "abc%b"
+msgstr "xyz%B"
+# Valid: type compatibility (argument list)
+msgid "abc%f"
+msgstr "xyz%g"
+# Valid: type compatibility (argument list)
+msgid "abc%f"
+msgstr "xyz%G"
+# Valid: type compatibility (argument list)
+msgid "abc%f"
+msgstr "xyz%e"
+# Valid: type compatibility (argument list)
+msgid "abc%f"
+msgstr "xyz%E"
+# Valid: type compatibility (argument list)
+msgid "abc%f"
+msgstr "xyz%a"
+# Valid: type compatibility (argument list)
+msgid "abc%f"
+msgstr "xyz%A"
+# Valid: type compatibility (argument list)
+msgid "abc%g"
+msgstr "xyz%G"
+# Valid: type compatibility (argument list)
+msgid "abc%g"
+msgstr "xyz%e"
+# Valid: type compatibility (argument list)
+msgid "abc%g"
+msgstr "xyz%E"
+# Valid: type compatibility (argument list)
+msgid "abc%g"
+msgstr "xyz%a"
+# Valid: type compatibility (argument list)
+msgid "abc%g"
+msgstr "xyz%A"
+# Valid: type compatibility (argument list)
+msgid "abc%G"
+msgstr "xyz%e"
+# Valid: type compatibility (argument list)
+msgid "abc%G"
+msgstr "xyz%E"
+# Valid: type compatibility (argument list)
+msgid "abc%G"
+msgstr "xyz%a"
+# Valid: type compatibility (argument list)
+msgid "abc%G"
+msgstr "xyz%A"
+# Valid: type compatibility (argument list)
+msgid "abc%e"
+msgstr "xyz%E"
+# Valid: type compatibility (argument list)
+msgid "abc%e"
+msgstr "xyz%a"
+# Valid: type compatibility (argument list)
+msgid "abc%e"
+msgstr "xyz%A"
+# Valid: type compatibility (argument list)
+msgid "abc%E"
+msgstr "xyz%a"
+# Valid: type compatibility (argument list)
+msgid "abc%E"
+msgstr "xyz%A"
+# Valid: type compatibility (argument list)
+msgid "abc%a"
+msgstr "xyz%A"
+# Valid: type compatibility (named)
+msgid "abc%<foo>d"
+msgstr "xyz%<foo>x"
+# Valid: type compatibility (named)
+msgid "abc%<foo>s"
+msgstr "xyz%{foo}"
+# Valid: type compatibility (named)
+msgid "abc%{foo}"
+msgstr "xyz%<foo>s"
+# Invalid: type incompatibility (argument list)
+msgid "abc%c"
+msgstr "xyz%s"
+# Invalid: type incompatibility (argument list)
+msgid "abc%c"
+msgstr "xyz%.0s"
+# Invalid: type incompatibility (argument list)
+msgid "abc%c"
+msgstr "xyz%p"
+# Invalid: type incompatibility (argument list)
+msgid "abc%c"
+msgstr "xyz%i"
+# Invalid: type incompatibility (argument list)
+msgid "abc%c"
+msgstr "xyz%e"
+# Invalid: type incompatibility (argument list)
+msgid "abc%s"
+msgstr "xyz%p"
+# Invalid: type incompatibility (argument list)
+msgid "abc%s"
+msgstr "xyz%i"
+# Invalid: type incompatibility (argument list)
+msgid "abc%.0s"
+msgstr "xyz%i"
+# Invalid: type incompatibility (argument list)
+msgid "abc%s"
+msgstr "xyz%e"
+# Invalid: type incompatibility (argument list)
+msgid "abc%.0s"
+msgstr "xyz%e"
+# Invalid: type incompatibility (argument list)
+msgid "abc%p"
+msgstr "xyz%i"
+# Invalid: type incompatibility (argument list)
+msgid "abc%p"
+msgstr "xyz%e"
+# Invalid: type incompatibility (argument list)
+msgid "abc%i"
+msgstr "xyz%e"
+# Invalid: type incompatibility (named)
+msgid "abc%<foo>c"
+msgstr "xyz%<foo>s"
+# Invalid: type incompatibility (named)
+msgid "abc%<foo>c"
+msgstr "xyz%{foo}"
+# Invalid: type incompatibility (named)
+msgid "abc%<foo>c"
+msgstr "xyz%<foo>.0s"
+# Invalid: type incompatibility (named)
+msgid "abc%<foo>c"
+msgstr "xyz%.0{foo}"
+# Invalid: type incompatibility (named)
+msgid "abc%<foo>c"
+msgstr "xyz%<foo>p"
+# Invalid: type incompatibility (named)
+msgid "abc%<foo>c"
+msgstr "xyz%<foo>i"
+# Invalid: type incompatibility (named)
+msgid "abc%<foo>c"
+msgstr "xyz%<foo>e"
+# Invalid: type incompatibility (named)
+msgid "abc%<foo>s"
+msgstr "xyz%<foo>p"
+# Invalid: type incompatibility (named)
+msgid "abc%{foo}"
+msgstr "xyz%<foo>p"
+# Invalid: type incompatibility (named)
+msgid "abc%<foo>s"
+msgstr "xyz%<foo>i"
+# Invalid: type incompatibility (named)
+msgid "abc%{foo}"
+msgstr "xyz%<foo>i"
+# Invalid: type incompatibility (named)
+msgid "abc%<foo>.0s"
+msgstr "xyz%<foo>i"
+# Invalid: type incompatibility (named)
+msgid "abc%.0{foo}"
+msgstr "xyz%<foo>i"
+# Invalid: type incompatibility (named)
+msgid "abc%<foo>s"
+msgstr "xyz%<foo>e"
+# Invalid: type incompatibility (named)
+msgid "abc%{foo}"
+msgstr "xyz%<foo>e"
+# Invalid: type incompatibility (named)
+msgid "abc%<foo>.0s"
+msgstr "xyz%<foo>e"
+# Invalid: type incompatibility (named)
+msgid "abc%.0{foo}"
+msgstr "xyz%<foo>e"
+# Invalid: type incompatibility (named)
+msgid "abc%<foo>p"
+msgstr "xyz%<foo>i"
+# Invalid: type incompatibility (named)
+msgid "abc%<foo>p"
+msgstr "xyz%<foo>e"
+# Invalid: type incompatibility (named)
+msgid "abc%<foo>i"
+msgstr "xyz%<foo>e"
+# Invalid: type incompatibility for width (argument list)
+msgid "abc%g%*g"
+msgstr "xyz%*g%g"
+EOF
+
+: ${MSGFMT=msgfmt}
+n=0
+while read comment; do
+ read msgid_line
+ read msgstr_line
+ n=`expr $n + 1`
+ cat <<EOF > f-r-2-$n.po
+#, ruby-format
+${msgid_line}
+${msgstr_line}
+EOF
+ fail=
+ if echo "$comment" | grep 'Valid:' > /dev/null; then
+ if ${MSGFMT} --check-format -o f-r-2-$n.mo f-r-2-$n.po; then
+ :
+ else
+ fail=yes
+ fi
+ else
+ ${MSGFMT} --check-format -o f-r-2-$n.mo f-r-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-r-2-$n.po 1>&2
+ Exit 1
+ fi
+ rm -f f-r-2-$n.po f-r-2-$n.mo
+done < f-r-2.data
+
+Exit 0
--- /dev/null
+#!/bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test of Ruby support.
+
+(rxgettext --version) >/dev/null 2>/dev/null \
+ || { echo "Skipping test: rxgettext not found"; Exit 77; }
+
+cat <<\EOF > xg-ru-1.rb
+_("abc")
+# Some comment.
+_("abc%%def")
+_("abc%{foo}def")
+# xgettext: no-ruby-format
+_("abc%{bar}def")
+_("some %d people")
+EOF
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --add-comments --no-location -o xg-ru-1.tmp xg-ru-1.rb 2>xg-ru-1.err
+test $? = 0 || { cat xg-ru-1.err; Exit 1; }
+func_filter_POT_Creation_Date xg-ru-1.tmp xg-ru-1.pot
+
+cat <<\EOF > xg-ru-1.ok
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "abc"
+msgstr ""
+
+#. Some comment.
+#, ruby-format
+msgid "abc%%def"
+msgstr ""
+
+#, ruby-format
+msgid "abc%{foo}def"
+msgstr ""
+
+#, no-ruby-format
+msgid "abc%{bar}def"
+msgstr ""
+
+#, ruby-format
+msgid "some %d people"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-ru-1.ok xg-ru-1.pot
+result=$?
+
+exit $result