Skip to content

Instantly share code, notes, and snippets.

@nox
Last active October 2, 2015 20:57
Show Gist options
  • Save nox/2319273 to your computer and use it in GitHub Desktop.
Save nox/2319273 to your computer and use it in GitHub Desktop.
Gettext support for Javascript
diff --git c/gettext-tools/libgettextpo/Makefile.am w/gettext-tools/libgettextpo/Makefile.am
index a96c36d..245f333 100644
--- c/gettext-tools/libgettextpo/Makefile.am
+++ w/gettext-tools/libgettextpo/Makefile.am
@@ -70,6 +70,7 @@ libgettextpo_la_AUXSOURCES = \
../src/format-librep.c \
../src/format-scheme.c \
../src/format-java.c \
+ ../src/format-javascript.c \
../src/format-csharp.c \
../src/format-awk.c \
../src/format-pascal.c \
diff --git c/gettext-tools/libgettextpo/Makefile.in w/gettext-tools/libgettextpo/Makefile.in
index 7488d45..544d664 100644
--- c/gettext-tools/libgettextpo/Makefile.in
+++ w/gettext-tools/libgettextpo/Makefile.in
@@ -323,7 +323,7 @@ am__libgettextpo_la_SOURCES_DIST = gettext-po.c ../src/str-list.c \
../src/plural-table.c ../src/format-c.c ../src/format-sh.c \
../src/format-python.c ../src/format-lisp.c \
../src/format-elisp.c ../src/format-librep.c \
- ../src/format-scheme.c ../src/format-java.c \
+ ../src/format-scheme.c ../src/format-java.c ../src/format-javascript.c \
../src/format-csharp.c ../src/format-awk.c \
../src/format-pascal.c ../src/format-ycp.c ../src/format-tcl.c \
../src/format-perl.c ../src/format-perl-brace.c \
@@ -339,7 +339,7 @@ am__objects_1 = str-list.lo dir-list.lo message.lo msgl-ascii.lo \
read-po.lo read-catalog-abstract.lo read-catalog.lo \
plural-table.lo format-c.lo format-sh.lo format-python.lo \
format-lisp.lo format-elisp.lo format-librep.lo \
- format-scheme.lo format-java.lo format-csharp.lo format-awk.lo \
+ format-scheme.lo format-java.lo format-javascript.lo format-csharp.lo format-awk.lo \
format-pascal.lo format-ycp.lo format-tcl.lo format-perl.lo \
format-perl-brace.lo format-php.lo format-gcc-internal.lo \
format-gfc-internal.lo format-qt.lo format-qt-plural.lo \
@@ -1459,6 +1459,7 @@ libgettextpo_la_AUXSOURCES = \
../src/format-librep.c \
../src/format-scheme.c \
../src/format-java.c \
+ ../src/format-javascript.c \
../src/format-csharp.c \
../src/format-awk.c \
../src/format-pascal.c \
@@ -1725,6 +1726,10 @@ format-java.lo: ../src/format-java.c
$(AM_V_CC) @AM_BACKSLASH@
$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o format-java.lo `test -f '../src/format-java.c' || echo '$(srcdir)/'`../src/format-java.c
+format-javascript.lo: ../src/format-javascript.c
+ $(AM_V_CC) @AM_BACKSLASH@
+ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o format-javascript.lo `test -f '../src/format-javascript.c' || echo '$(srcdir)/'`../src/format-javascript.c
+
format-csharp.lo: ../src/format-csharp.c
$(AM_V_CC) @AM_BACKSLASH@
$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o format-csharp.lo `test -f '../src/format-csharp.c' || echo '$(srcdir)/'`../src/format-csharp.c
diff --git c/gettext-tools/src/Makefile.am w/gettext-tools/src/Makefile.am
index d9e7646..2f3b090 100644
--- c/gettext-tools/src/Makefile.am
+++ w/gettext-tools/src/Makefile.am
@@ -51,7 +51,7 @@ write-qt.h \
po-time.h plural-table.h lang-table.h format.h filters.h \
xgettext.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-tcl.h x-perl.h x-php.h x-stringtable.h x-rst.h x-glade.h x-javascript.h
EXTRA_DIST += FILES project-id ChangeLog.0
@@ -117,7 +117,7 @@ format-c.c format-sh.c format-python.c format-lisp.c format-elisp.c \
format-librep.c format-scheme.c format-java.c format-csharp.c format-awk.c \
format-pascal.c format-ycp.c format-tcl.c format-perl.c format-perl-brace.c \
format-php.c format-gcc-internal.c format-gfc-internal.c \
-format-qt.c format-qt-plural.c format-kde.c format-boost.c
+format-qt.c format-qt-plural.c format-kde.c format-boost.c format-javascript.c
# libgettextsrc contains all code that is needed by at least two programs.
libgettextsrc_la_SOURCES = \
@@ -155,7 +155,7 @@ endif
xgettext_SOURCES += \
x-c.c x-po.c x-sh.c x-python.c x-lisp.c x-elisp.c x-librep.c x-scheme.c \
x-smalltalk.c x-java.c x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c \
- x-rst.c x-glade.c
+ x-rst.c x-glade.c x-javascript.c
if !WOE32DLL
msgattrib_SOURCES = msgattrib.c
else
diff --git c/gettext-tools/src/Makefile.in w/gettext-tools/src/Makefile.in
index f9842d2..457d524 100644
--- c/gettext-tools/src/Makefile.in
+++ w/gettext-tools/src/Makefile.in
@@ -306,7 +306,7 @@ am__libgettextsrc_la_SOURCES_DIST = message.c po-error.c po-xerror.c \
format-java.c format-csharp.c format-awk.c format-pascal.c \
format-ycp.c format-tcl.c format-perl.c format-perl-brace.c \
format-php.c format-gcc-internal.c format-gfc-internal.c \
- format-qt.c format-qt-plural.c format-kde.c format-boost.c \
+ format-qt.c format-qt-plural.c format-kde.c format-boost.c format-javascript.c \
../woe32dll/c++format.cc ../woe32dll/gettextsrc-exports.c
am__objects_1 = message.lo po-error.lo po-xerror.lo \
read-catalog-abstract.lo po-lex.lo po-gram-gen.lo \
@@ -322,7 +322,7 @@ am__objects_1 = message.lo po-error.lo po-xerror.lo \
@WOE32DLL_FALSE@ format-php.lo format-gcc-internal.lo \
@WOE32DLL_FALSE@ format-gfc-internal.lo format-qt.lo \
@WOE32DLL_FALSE@ format-qt-plural.lo format-kde.lo \
-@WOE32DLL_FALSE@ format-boost.lo
+@WOE32DLL_FALSE@ format-boost.lo format-javascript.lo
@WOE32DLL_TRUE@am__objects_2 = c++format.lo format-c.lo format-sh.lo \
@WOE32DLL_TRUE@ format-python.lo format-lisp.lo format-elisp.lo \
@WOE32DLL_TRUE@ format-librep.lo format-scheme.lo \
@@ -332,7 +332,7 @@ am__objects_1 = message.lo po-error.lo po-xerror.lo \
@WOE32DLL_TRUE@ format-php.lo format-gcc-internal.lo \
@WOE32DLL_TRUE@ format-gfc-internal.lo format-qt.lo \
@WOE32DLL_TRUE@ format-qt-plural.lo format-kde.lo \
-@WOE32DLL_TRUE@ format-boost.lo
+@WOE32DLL_TRUE@ format-boost.lo format-javascript.lo
@WOE32DLL_TRUE@am__objects_3 = gettextsrc-exports.lo
am_libgettextsrc_la_OBJECTS = $(am__objects_1) read-catalog.lo \
color.lo write-catalog.lo write-properties.lo \
@@ -454,7 +454,7 @@ urlget_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
am__xgettext_SOURCES_DIST = xgettext.c x-c.c x-po.c x-sh.c x-python.c \
x-lisp.c x-elisp.c x-librep.c x-scheme.c x-smalltalk.c \
x-java.c x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c \
- x-rst.c x-glade.c ../woe32dll/c++xgettext.cc
+ x-rst.c x-glade.c x-javascript.c ../woe32dll/c++xgettext.cc
@WOE32DLL_FALSE@am_xgettext_OBJECTS = xgettext-xgettext.$(OBJEXT) \
@WOE32DLL_FALSE@ xgettext-x-c.$(OBJEXT) xgettext-x-po.$(OBJEXT) \
@WOE32DLL_FALSE@ xgettext-x-sh.$(OBJEXT) \
@@ -472,7 +472,8 @@ am__xgettext_SOURCES_DIST = xgettext.c x-c.c x-po.c x-sh.c x-python.c \
@WOE32DLL_FALSE@ xgettext-x-perl.$(OBJEXT) \
@WOE32DLL_FALSE@ xgettext-x-php.$(OBJEXT) \
@WOE32DLL_FALSE@ xgettext-x-rst.$(OBJEXT) \
-@WOE32DLL_FALSE@ xgettext-x-glade.$(OBJEXT)
+@WOE32DLL_FALSE@ xgettext-x-glade.$(OBJEXT) \
+@WOE32DLL_FALSE@ xgettext-x-javascript.$(OBJEXT)
@WOE32DLL_TRUE@am_xgettext_OBJECTS = xgettext-c++xgettext.$(OBJEXT) \
@WOE32DLL_TRUE@ xgettext-x-c.$(OBJEXT) xgettext-x-po.$(OBJEXT) \
@WOE32DLL_TRUE@ xgettext-x-sh.$(OBJEXT) \
@@ -490,7 +491,8 @@ am__xgettext_SOURCES_DIST = xgettext.c x-c.c x-po.c x-sh.c x-python.c \
@WOE32DLL_TRUE@ xgettext-x-perl.$(OBJEXT) \
@WOE32DLL_TRUE@ xgettext-x-php.$(OBJEXT) \
@WOE32DLL_TRUE@ xgettext-x-rst.$(OBJEXT) \
-@WOE32DLL_TRUE@ xgettext-x-glade.$(OBJEXT)
+@WOE32DLL_TRUE@ xgettext-x-glade.$(OBJEXT) \
+@WOE32DLL_TRUE@ xgettext-x-javascript.$(OBJEXT)
xgettext_OBJECTS = $(am_xgettext_OBJECTS)
DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
depcomp =
@@ -1489,7 +1491,7 @@ write-qt.h \
po-time.h plural-table.h lang-table.h format.h filters.h \
xgettext.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-tcl.h x-perl.h x-php.h x-stringtable.h x-rst.h x-glade.h x-javascript.h
aliaspath = $(localedir)
jardir = $(datadir)/gettext
@@ -1531,17 +1533,17 @@ dir-list.c str-list.c
@WOE32DLL_FALSE@ format-perl.c format-perl-brace.c format-php.c \
@WOE32DLL_FALSE@ format-gcc-internal.c format-gfc-internal.c \
@WOE32DLL_FALSE@ format-qt.c format-qt-plural.c format-kde.c \
-@WOE32DLL_FALSE@ format-boost.c
+@WOE32DLL_FALSE@ format-boost.c format-javascript.c
@WOE32DLL_TRUE@FORMAT_SOURCE = ../woe32dll/c++format.cc \
@WOE32DLL_TRUE@ format-invalid.h format-c.c format-sh.c \
@WOE32DLL_TRUE@ format-python.c format-lisp.c format-elisp.c \
-@WOE32DLL_TRUE@ format-librep.c format-scheme.c format-java.c \
+@WOE32DLL_TRUE@ format-librep.c format-scheme.c format-javascript.c \
@WOE32DLL_TRUE@ format-csharp.c format-awk.c format-pascal.c \
@WOE32DLL_TRUE@ format-ycp.c format-tcl.c format-perl.c \
@WOE32DLL_TRUE@ format-perl-brace.c format-php.c \
@WOE32DLL_TRUE@ format-gcc-internal.c format-gfc-internal.c \
@WOE32DLL_TRUE@ format-qt.c format-qt-plural.c format-kde.c \
-@WOE32DLL_TRUE@ format-boost.c
+@WOE32DLL_TRUE@ format-boost.c format-javascript.c
# libgettextsrc contains all code that is needed by at least two programs.
libgettextsrc_la_SOURCES = $(COMMON_SOURCE) read-catalog.c color.c \
@@ -1569,12 +1571,12 @@ msgunfmt_SOURCES = msgunfmt.c read-mo.c read-java.c read-csharp.c \
@WOE32DLL_FALSE@ x-python.c x-lisp.c x-elisp.c x-librep.c \
@WOE32DLL_FALSE@ x-scheme.c x-smalltalk.c x-java.c x-csharp.c \
@WOE32DLL_FALSE@ x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c \
-@WOE32DLL_FALSE@ x-rst.c x-glade.c
+@WOE32DLL_FALSE@ x-rst.c x-glade.c x-javascript.c
@WOE32DLL_TRUE@xgettext_SOURCES = ../woe32dll/c++xgettext.cc x-c.c \
@WOE32DLL_TRUE@ x-po.c x-sh.c x-python.c x-lisp.c x-elisp.c \
@WOE32DLL_TRUE@ x-librep.c x-scheme.c x-smalltalk.c x-java.c \
@WOE32DLL_TRUE@ x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c \
-@WOE32DLL_TRUE@ x-php.c x-rst.c x-glade.c
+@WOE32DLL_TRUE@ x-php.c x-rst.c x-glade.c x-javascript.c
@WOE32DLL_FALSE@msgattrib_SOURCES = msgattrib.c
@WOE32DLL_TRUE@msgattrib_SOURCES = ../woe32dll/c++msgattrib.cc
@WOE32DLL_FALSE@msgcat_SOURCES = msgcat.c
@@ -2507,6 +2509,14 @@ xgettext-x-glade.obj: x-glade.c
$(AM_V_CC) @AM_BACKSLASH@
$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-glade.obj `if test -f 'x-glade.c'; then $(CYGPATH_W) 'x-glade.c'; else $(CYGPATH_W) '$(srcdir)/x-glade.c'; fi`
+xgettext-x-javascript.o: x-javascript.c
+ $(AM_V_CC) @AM_BACKSLASH@
+ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-javascript.o `test -f 'x-javascript.c' || echo '$(srcdir)/'`x-javascript.c
+
+xgettext-x-javascript.obj: x-javascript.c
+ $(AM_V_CC) @AM_BACKSLASH@
+ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-javascript.obj `if test -f 'x-javascript.c'; then $(CYGPATH_W) 'x-javascript.c'; else $(CYGPATH_W) '$(srcdir)/x-javascript.c'; fi`
+
.cc.o:
$(AM_V_CXX) @AM_BACKSLASH@
$(CXXCOMPILE) -c -o $@ $<
diff --git c/gettext-tools/src/format-javascript.c w/gettext-tools/src/format-javascript.c
new file mode 100644
index 0000000..5705972
--- /dev/null
+++ w/gettext-tools/src/format-javascript.c
@@ -0,0 +1,669 @@
+/* JavaScript format strings.
+ Copyright (C) 2001-2004, 2006-2009 Free Software Foundation, Inc.
+ Written by Andreas Stricker <[email protected]>, 2010.
+ It's based on python format module from Bruno Haible.
+
+ 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 <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "format.h"
+#include "c-ctype.h"
+#include "xalloc.h"
+#include "xvasprintf.h"
+#include "format-invalid.h"
+#include "gettext.h"
+
+#define _(str) gettext (str)
+
+/* Gettext itself doesn't know any format string. But there are
+ plenty of libraries using any kind of format strings:
+ * JS-Gettext allows %1 %2 ...
+ * Prototype JS has templates #{name}
+ * ExtJS has String.format with {1} {2} ... and Templates with {name}
+ */
+
+enum format_arg_type
+{
+ FAT_NONE,
+ FAT_ANY,
+ FAT_CHARACTER,
+ FAT_STRING,
+ FAT_INTEGER,
+ FAT_FLOAT
+};
+
+struct named_arg
+{
+ char *name;
+ enum format_arg_type type;
+};
+
+struct unnamed_arg
+{
+ enum format_arg_type type;
+};
+
+struct spec
+{
+ unsigned int directives;
+ unsigned int named_arg_count;
+ unsigned int unnamed_arg_count;
+ unsigned int allocated;
+ struct named_arg *named;
+ struct unnamed_arg *unnamed;
+};
+
+/* Locale independent test for a decimal digit.
+ Argument can be 'char' or 'unsigned char'. (Whereas the argument of
+ <ctype.h> isdigit must be an 'unsigned char'.) */
+#undef isdigit
+#define isdigit(c) ((unsigned int) ((c) - '0') < 10)
+
+
+static int
+named_arg_compare (const void *p1, const void *p2)
+{
+ return strcmp (((const struct named_arg *) p1)->name,
+ ((const struct named_arg *) p2)->name);
+}
+
+#define INVALID_MIXES_NAMED_UNNAMED() \
+ xstrdup (_("The string refers to arguments both through argument names and through unnamed argument specifications."))
+
+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;
+
+ spec.directives = 0;
+ spec.named_arg_count = 0;
+ spec.unnamed_arg_count = 0;
+ spec.allocated = 0;
+ spec.named = NULL;
+ spec.unnamed = NULL;
+
+ for (; *format != '\0';)
+ if (*format++ == '%')
+ {
+ /* A directive. */
+ char *name = NULL;
+ bool zero_precision = false;
+ enum format_arg_type type;
+
+ FDI_SET (format - 1, FMTDIR_START);
+ spec.directives++;
+
+ if (*format == '(')
+ {
+ unsigned int depth;
+ const char *name_start;
+ const char *name_end;
+ size_t n;
+
+ name_start = ++format;
+ depth = 0;
+ for (; *format != '\0'; format++)
+ {
+ if (*format == '(')
+ depth++;
+ else if (*format == ')')
+ {
+ if (depth == 0)
+ break;
+ else
+ depth--;
+ }
+ }
+ if (*format == '\0')
+ {
+ *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
+ name_end = format++;
+
+ n = name_end - name_start;
+ name = XNMALLOC (n + 1, char);
+ memcpy (name, name_start, n);
+ name[n] = '\0';
+ }
+
+ while (*format == '-' || *format == '+' || *format == ' '
+ || *format == '#' || *format == '0')
+ format++;
+
+ if (*format == '*')
+ {
+ format++;
+
+ /* Named and unnamed specifications are exclusive. */
+ if (spec.named_arg_count > 0)
+ {
+ *invalid_reason = INVALID_MIXES_NAMED_UNNAMED ();
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (spec.allocated == spec.unnamed_arg_count)
+ {
+ spec.allocated = 2 * spec.allocated + 1;
+ spec.unnamed = (struct unnamed_arg *) xrealloc (spec.unnamed, spec.allocated * sizeof (struct unnamed_arg));
+ }
+ spec.unnamed[spec.unnamed_arg_count].type = FAT_INTEGER;
+ spec.unnamed_arg_count++;
+ }
+ else if (isdigit (*format))
+ {
+ do format++; while (isdigit (*format));
+ }
+
+ if (*format == '.')
+ {
+ format++;
+
+ if (*format == '*')
+ {
+ format++;
+
+ /* Named and unnamed specifications are exclusive. */
+ if (spec.named_arg_count > 0)
+ {
+ *invalid_reason = INVALID_MIXES_NAMED_UNNAMED ();
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (spec.allocated == spec.unnamed_arg_count)
+ {
+ spec.allocated = 2 * spec.allocated + 1;
+ spec.unnamed = (struct unnamed_arg *) xrealloc (spec.unnamed, spec.allocated * sizeof (struct unnamed_arg));
+ }
+ spec.unnamed[spec.unnamed_arg_count].type = FAT_INTEGER;
+ spec.unnamed_arg_count++;
+ }
+ else if (isdigit (*format))
+ {
+ zero_precision = true;
+ do
+ {
+ if (*format != '0')
+ zero_precision = false;
+ format++;
+ }
+ while (isdigit (*format));
+ }
+ }
+
+ if (*format == 'h' || *format == 'l' || *format == 'L')
+ format++;
+
+ switch (*format)
+ {
+ case '%':
+ type = FAT_NONE;
+ break;
+ case 'c':
+ type = FAT_CHARACTER;
+ break;
+ case 's': case 'r':
+ type = (zero_precision ? FAT_ANY : FAT_STRING);
+ break;
+ case 'i': case 'd': case 'u': case 'o': case 'x': case 'X':
+ type = FAT_INTEGER;
+ break;
+ case 'e': case 'E': case 'f': case 'g': case 'G':
+ type = FAT_FLOAT;
+ 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;
+ }
+
+ if (name != NULL)
+ {
+ /* Named argument. */
+
+ /* Named and unnamed specifications are exclusive. */
+ if (spec.unnamed_arg_count > 0)
+ {
+ *invalid_reason = INVALID_MIXES_NAMED_UNNAMED ();
+ FDI_SET (format, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (spec.allocated == spec.named_arg_count)
+ {
+ spec.allocated = 2 * spec.allocated + 1;
+ spec.named = (struct named_arg *) xrealloc (spec.named, spec.allocated * sizeof (struct named_arg));
+ }
+ spec.named[spec.named_arg_count].name = name;
+ spec.named[spec.named_arg_count].type = type;
+ spec.named_arg_count++;
+ }
+ else if (*format != '%')
+ {
+ /* Unnamed argument. */
+
+ /* Named and unnamed specifications are exclusive. */
+ if (spec.named_arg_count > 0)
+ {
+ *invalid_reason = INVALID_MIXES_NAMED_UNNAMED ();
+ FDI_SET (format, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (spec.allocated == spec.unnamed_arg_count)
+ {
+ spec.allocated = 2 * spec.allocated + 1;
+ spec.unnamed = (struct unnamed_arg *) xrealloc (spec.unnamed, spec.allocated * sizeof (struct unnamed_arg));
+ }
+ spec.unnamed[spec.unnamed_arg_count].type = type;
+ spec.unnamed_arg_count++;
+ }
+
+ FDI_SET (format, FMTDIR_END);
+
+ format++;
+ }
+
+ /* Sort the named argument array, and eliminate duplicates. */
+ if (spec.named_arg_count > 1)
+ {
+ unsigned int i, j;
+ bool err;
+
+ qsort (spec.named, spec.named_arg_count, sizeof (struct named_arg),
+ named_arg_compare);
+
+ /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */
+ err = false;
+ for (i = j = 0; i < spec.named_arg_count; i++)
+ if (j > 0 && strcmp (spec.named[i].name, spec.named[j-1].name) == 0)
+ {
+ enum format_arg_type type1 = spec.named[i].type;
+ enum format_arg_type type2 = spec.named[j-1].type;
+ enum format_arg_type type_both;
+
+ if (type1 == type2 || type2 == FAT_ANY)
+ type_both = type1;
+ else if (type1 == FAT_ANY)
+ type_both = type2;
+ else
+ {
+ /* Incompatible types. */
+ type_both = FAT_NONE;
+ if (!err)
+ *invalid_reason =
+ xasprintf (_("The string refers to the argument named '%s' in incompatible ways."), spec.named[i].name);
+ err = true;
+ }
+
+ spec.named[j-1].type = type_both;
+ free (spec.named[i].name);
+ }
+ else
+ {
+ if (j < i)
+ {
+ spec.named[j].name = spec.named[i].name;
+ spec.named[j].type = spec.named[i].type;
+ }
+ j++;
+ }
+ spec.named_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.named != NULL)
+ {
+ unsigned int i;
+ for (i = 0; i < spec.named_arg_count; i++)
+ free (spec.named[i].name);
+ free (spec.named);
+ }
+ if (spec.unnamed != NULL)
+ free (spec.unnamed);
+ return NULL;
+}
+
+static void
+format_free (void *descr)
+{
+ struct spec *spec = (struct spec *) descr;
+
+ if (spec->named != NULL)
+ {
+ unsigned int i;
+ for (i = 0; i < spec->named_arg_count; i++)
+ free (spec->named[i].name);
+ free (spec->named);
+ }
+ if (spec->unnamed != NULL)
+ free (spec->unnamed);
+ 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->named_arg_count > 0 && spec2->unnamed_arg_count > 0)
+ {
+ if (error_logger)
+ error_logger (_("format specifications in '%s' expect a mapping, those in '%s' expect a tuple"),
+ pretty_msgid, pretty_msgstr);
+ err = true;
+ }
+ else if (spec1->unnamed_arg_count > 0 && spec2->named_arg_count > 0)
+ {
+ if (error_logger)
+ error_logger (_("format specifications in '%s' expect a tuple, those in '%s' expect a mapping"),
+ pretty_msgid, pretty_msgstr);
+ err = true;
+ }
+ else
+ {
+ if (spec1->named_arg_count + spec2->named_arg_count > 0)
+ {
+ unsigned int i, j;
+ unsigned int n1 = spec1->named_arg_count;
+ unsigned int n2 = spec2->named_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 :
+ strcmp (spec1->named[i].name, spec2->named[j].name));
+
+ if (cmp > 0)
+ {
+ if (error_logger)
+ error_logger (_("a format specification for argument '%s', as in '%s', doesn't exist in '%s'"),
+ spec2->named[j].name, pretty_msgstr,
+ pretty_msgid);
+ err = true;
+ break;
+ }
+ else if (cmp < 0)
+ {
+ if (equality)
+ {
+ if (error_logger)
+ error_logger (_("a format specification for argument '%s' doesn't exist in '%s'"),
+ spec1->named[i].name, 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 (strcmp (spec1->named[i].name, spec2->named[j].name) == 0)
+ {
+ if (!(spec1->named[i].type == spec2->named[j].type
+ || (!equality
+ && (spec1->named[i].type == FAT_ANY
+ || spec2->named[j].type == FAT_ANY))))
+ {
+ if (error_logger)
+ error_logger (_("format specifications in '%s' and '%s' for argument '%s' are not the same"),
+ pretty_msgid, pretty_msgstr,
+ spec2->named[j].name);
+ err = true;
+ break;
+ }
+ j++, i++;
+ }
+ else
+ i++;
+ }
+ }
+
+ if (spec1->unnamed_arg_count + spec2->unnamed_arg_count > 0)
+ {
+ unsigned int i;
+
+ /* Check the argument types are the same. */
+ if (spec1->unnamed_arg_count != spec2->unnamed_arg_count)
+ {
+ if (error_logger)
+ error_logger (_("number of format specifications in '%s' and '%s' does not match"),
+ pretty_msgid, pretty_msgstr);
+ err = true;
+ }
+ else
+ for (i = 0; i < spec2->unnamed_arg_count; i++)
+ if (!(spec1->unnamed[i].type == spec2->unnamed[i].type
+ || (!equality
+ && (spec1->unnamed[i].type == FAT_ANY
+ || spec2->unnamed[i].type == FAT_ANY))))
+ {
+ if (error_logger)
+ error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"),
+ pretty_msgid, pretty_msgstr, i + 1);
+ err = true;
+ }
+ }
+ }
+
+ return err;
+}
+
+
+struct formatstring_parser formatstring_javascript =
+{
+ format_parse,
+ format_free,
+ format_get_number_of_directives,
+ NULL,
+ format_check
+};
+
+
+unsigned int
+get_javascript_format_unnamed_arg_count (const char *string)
+{
+ /* Parse the format string. */
+ char *invalid_reason = NULL;
+ struct spec *descr =
+ (struct spec *) format_parse (string, false, NULL, &invalid_reason);
+
+ if (descr != NULL)
+ {
+ unsigned int result = descr->unnamed_arg_count;
+
+ format_free (descr);
+ return result;
+ }
+ else
+ {
+ free (invalid_reason);
+ return 0;
+ }
+}
+
+
+#ifdef TEST
+
+/* Test program: Print the argument list specification returned by
+ format_parse for strings read from standard input. */
+
+#include <stdio.h>
+
+static void
+format_print (void *descr)
+{
+ struct spec *spec = (struct spec *) descr;
+ unsigned int i;
+
+ if (spec == NULL)
+ {
+ printf ("INVALID");
+ return;
+ }
+
+ if (spec->named_arg_count > 0)
+ {
+ if (spec->unnamed_arg_count > 0)
+ abort ();
+
+ printf ("{");
+ for (i = 0; i < spec->named_arg_count; i++)
+ {
+ if (i > 0)
+ printf (", ");
+ printf ("'%s':", spec->named[i].name);
+ switch (spec->named[i].type)
+ {
+ case FAT_ANY:
+ printf ("*");
+ break;
+ case FAT_CHARACTER:
+ printf ("c");
+ break;
+ case FAT_STRING:
+ printf ("s");
+ break;
+ case FAT_INTEGER:
+ printf ("i");
+ break;
+ case FAT_FLOAT:
+ printf ("f");
+ break;
+ default:
+ abort ();
+ }
+ }
+ printf ("}");
+ }
+ else
+ {
+ printf ("(");
+ for (i = 0; i < spec->unnamed_arg_count; i++)
+ {
+ if (i > 0)
+ printf (" ");
+ switch (spec->unnamed[i].type)
+ {
+ case FAT_ANY:
+ printf ("*");
+ break;
+ case FAT_CHARACTER:
+ printf ("c");
+ break;
+ case FAT_STRING:
+ printf ("s");
+ break;
+ case FAT_INTEGER:
+ printf ("i");
+ break;
+ case FAT_FLOAT:
+ printf ("f");
+ 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../intl -DHAVE_CONFIG_H -DTEST format-javascript.c ../gnulib-lib/libgettextlib.la"
+ * End:
+ */
+
+#endif /* TEST */
diff --git c/gettext-tools/src/format.c w/gettext-tools/src/format.c
index e6c5de9..22fc8c2 100644
--- c/gettext-tools/src/format.c
+++ w/gettext-tools/src/format.c
@@ -44,6 +44,7 @@ struct formatstring_parser *formatstring_parsers[NFORMATS] =
/* format_scheme */ &formatstring_scheme,
/* format_smalltalk */ &formatstring_smalltalk,
/* format_java */ &formatstring_java,
+ /* format_javascript */ &formatstring_javascript,
/* format_csharp */ &formatstring_csharp,
/* format_awk */ &formatstring_awk,
/* format_pascal */ &formatstring_pascal,
diff --git c/gettext-tools/src/format.h w/gettext-tools/src/format.h
index 60f0adc..679cd9a 100644
--- c/gettext-tools/src/format.h
+++ w/gettext-tools/src/format.h
@@ -105,6 +105,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_javascript;
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 c/gettext-tools/src/message.c w/gettext-tools/src/message.c
index 5162b06..e57ddab 100644
--- c/gettext-tools/src/message.c
+++ w/gettext-tools/src/message.c
@@ -44,6 +44,7 @@ const char *const format_language[NFORMATS] =
/* format_scheme */ "scheme",
/* format_smalltalk */ "smalltalk",
/* format_java */ "java",
+ /* format_javascript */ "javascript",
/* format_csharp */ "csharp",
/* format_awk */ "awk",
/* format_pascal */ "object-pascal",
@@ -72,6 +73,7 @@ const char *const format_language_pretty[NFORMATS] =
/* format_scheme */ "Scheme",
/* format_smalltalk */ "Smalltalk",
/* format_java */ "Java",
+ /* format_javascript */ "JavaScript",
/* format_csharp */ "C#",
/* format_awk */ "awk",
/* format_pascal */ "Object Pascal",
diff --git c/gettext-tools/src/message.h w/gettext-tools/src/message.h
index af9244a..0f200b1 100644
--- c/gettext-tools/src/message.h
+++ w/gettext-tools/src/message.h
@@ -53,6 +53,7 @@ enum format_type
format_scheme,
format_smalltalk,
format_java,
+ format_javascript,
format_csharp,
format_awk,
format_pascal,
@@ -68,7 +69,7 @@ enum format_type
format_kde,
format_boost
};
-#define NFORMATS 24 /* Number of format_type enum values. */
+#define NFORMATS 25 /* 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 c/gettext-tools/src/x-javascript.c w/gettext-tools/src/x-javascript.c
new file mode 100644
index 0000000..f97308c
--- /dev/null
+++ w/gettext-tools/src/x-javascript.c
@@ -0,0 +1,1809 @@
+/* xgettext JavaScript backend.
+ Copyright (C) 2002-2003, 2005-2009 Free Software Foundation, Inc.
+
+ This file was written by Andreas Stricker <[email protected]>, 2010
+ It's based on x-python from Bruno Haible.
+
+ 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 <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+/* Specification. */
+#include "x-javascript.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "message.h"
+#include "xgettext.h"
+#include "error.h"
+#include "error-progname.h"
+#include "progname.h"
+#include "basename.h"
+#include "xerror.h"
+#include "xvasprintf.h"
+#include "xalloc.h"
+#include "c-strstr.h"
+#include "c-ctype.h"
+#include "po-charset.h"
+#include "uniname.h"
+#include "unistr.h"
+#include "gettext.h"
+
+#define _(s) gettext(s)
+
+#define max(a,b) ((a) > (b) ? (a) : (b))
+
+#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
+
+/* The JavaScript aka ECMA-Script syntax is defined in ECMA-262
+ specification:
+ http://www.ecma-international.org/publications/standards/Ecma-262.htm */
+
+/* ====================== Keyword set customization. ====================== */
+
+/* If true extract all strings. */
+static bool extract_all = false;
+
+static hash_table keywords;
+static bool default_keywords = true;
+
+
+void
+x_javascript_extract_all ()
+{
+ extract_all = true;
+}
+
+
+void
+x_javascript_keyword (const char *name)
+{
+ if (name == NULL)
+ default_keywords = false;
+ else
+ {
+ const char *end;
+ struct callshape shape;
+ const char *colon;
+
+ if (keywords.table == NULL)
+ hash_init (&keywords, 100);
+
+ split_keywordspec (name, &end, &shape);
+
+ /* The characters between name and end should form a valid C identifier.
+ A colon means an invalid parse in split_keywordspec(). */
+ colon = strchr (name, ':');
+ if (colon == NULL || colon >= end)
+ insert_keyword_callshape (&keywords, name, end - name, &shape);
+ }
+}
+
+/* Finish initializing the keywords hash table.
+ Called after argument processing, before each file is processed. */
+static void
+init_keywords ()
+{
+ if (default_keywords)
+ {
+ /* When adding new keywords here, also update the documentation in
+ xgettext.texi! */
+ x_javascript_keyword ("gettext");
+ x_javascript_keyword ("ugettext");
+ x_javascript_keyword ("dgettext:2");
+ x_javascript_keyword ("ngettext:1,2");
+ x_javascript_keyword ("ungettext:1,2");
+ x_javascript_keyword ("dngettext:2,3");
+ x_javascript_keyword ("_");
+ default_keywords = false;
+ }
+}
+
+void
+init_flag_table_javascript ()
+{
+ xgettext_record_flag ("gettext:1:pass-javascript-format");
+ xgettext_record_flag ("ugettext:1:pass-javascript-format");
+ xgettext_record_flag ("dgettext:2:pass-javascript-format");
+ xgettext_record_flag ("ngettext:1:pass-javascript-format");
+ xgettext_record_flag ("ngettext:2:pass-javascript-format");
+ xgettext_record_flag ("ungettext:1:pass-javascript-format");
+ xgettext_record_flag ("ungettext:2:pass-javascript-format");
+ xgettext_record_flag ("dngettext:2:pass-javascript-format");
+ xgettext_record_flag ("dngettext:3:pass-javascript-format");
+ xgettext_record_flag ("_:1:pass-javascript-format");
+}
+
+
+/* ======================== Reading of characters. ======================== */
+
+/* Real filename, used in error messages about the input file. */
+static const char *real_file_name;
+
+/* Logical filename and line number, used to label the extracted messages. */
+static char *logical_file_name;
+static int line_number;
+
+/* The input file stream. */
+static FILE *fp;
+
+
+/* 1. line_number handling. */
+
+/* Maximum used, roughly a safer MB_LEN_MAX. */
+#define MAX_PHASE1_PUSHBACK 16
+static unsigned char phase1_pushback[MAX_PHASE1_PUSHBACK];
+static int phase1_pushback_length;
+
+/* Read the next single byte from the input file. */
+static int
+phase1_getc ()
+{
+ int c;
+
+ if (phase1_pushback_length)
+ c = phase1_pushback[--phase1_pushback_length];
+ else
+ {
+ c = getc (fp);
+
+ if (c == EOF)
+ {
+ if (ferror (fp))
+ error (EXIT_FAILURE, errno, _("error while reading \"%s\""),
+ real_file_name);
+ return EOF;
+ }
+ }
+
+ if (c == '\n')
+ ++line_number;
+
+ return c;
+}
+
+/* Supports MAX_PHASE1_PUSHBACK characters of pushback. */
+static void
+phase1_ungetc (int c)
+{
+ if (c != EOF)
+ {
+ if (c == '\n')
+ --line_number;
+
+ if (phase1_pushback_length == SIZEOF (phase1_pushback))
+ abort ();
+ phase1_pushback[phase1_pushback_length++] = c;
+ }
+}
+
+
+/* Phase 2: Conversion to Unicode.
+ For now, we expect Javascript files to be encoded as UTF-8 */
+
+/* End-of-file indicator for functions returning an UCS-4 character. */
+#define UEOF -1
+
+static lexical_context_ty lexical_context;
+
+static int phase2_pushback[max (9, UNINAME_MAX + 3)];
+static int phase2_pushback_length;
+
+/* Read the next Unicode UCS-4 character from the input file. */
+static int
+phase2_getc ()
+{
+ if (phase2_pushback_length)
+ return phase2_pushback[--phase2_pushback_length];
+
+ if (xgettext_current_source_encoding == po_charset_ascii)
+ {
+ int c = phase1_getc ();
+ if (c == EOF)
+ return UEOF;
+ if (!c_isascii (c))
+ {
+ multiline_error (xstrdup (""),
+ xasprintf ("%s\n%s\n",
+ non_ascii_error_message (lexical_context,
+ real_file_name,
+ line_number),
+ _("\
+Please specify the source encoding through --from-code\n")));
+ exit (EXIT_FAILURE);
+ }
+ return c;
+ }
+ else if (xgettext_current_source_encoding != po_charset_utf8)
+ {
+#if HAVE_ICONV
+ /* Use iconv on an increasing number of bytes. Read only as many bytes
+ through phase1_getc as needed. This is needed to give reasonable
+ interactive behaviour when fp is connected to an interactive tty. */
+ unsigned char buf[MAX_PHASE1_PUSHBACK];
+ size_t bufcount;
+ int c = phase1_getc ();
+ if (c == EOF)
+ return UEOF;
+ buf[0] = (unsigned char) c;
+ bufcount = 1;
+
+ for (;;)
+ {
+ unsigned char scratchbuf[6];
+ const char *inptr = (const char *) &buf[0];
+ size_t insize = bufcount;
+ char *outptr = (char *) &scratchbuf[0];
+ size_t outsize = sizeof (scratchbuf);
+
+ size_t res = iconv (xgettext_current_source_iconv,
+ (ICONV_CONST char **) &inptr, &insize,
+ &outptr, &outsize);
+ /* We expect that a character has been produced if and only if
+ some input bytes have been consumed. */
+ if ((insize < bufcount) != (outsize < sizeof (scratchbuf)))
+ abort ();
+ if (outsize == sizeof (scratchbuf))
+ {
+ /* No character has been produced. Must be an error. */
+ if (res != (size_t)(-1))
+ abort ();
+
+ if (errno == EILSEQ)
+ {
+ /* An invalid multibyte sequence was encountered. */
+ multiline_error (xstrdup (""),
+ xasprintf (_("\
+%s:%d: Invalid multibyte sequence.\n\
+Please specify the correct source encoding through --from-code\n"),
+ real_file_name, line_number));
+ exit (EXIT_FAILURE);
+ }
+ else if (errno == EINVAL)
+ {
+ /* An incomplete multibyte character. */
+ int c;
+
+ if (bufcount == MAX_PHASE1_PUSHBACK)
+ {
+ /* An overlong incomplete multibyte sequence was
+ encountered. */
+ multiline_error (xstrdup (""),
+ xasprintf (_("\
+%s:%d: Long incomplete multibyte sequence.\n\
+Please specify the correct source encoding through --from-code\n"),
+ real_file_name, line_number));
+ exit (EXIT_FAILURE);
+ }
+
+ /* Read one more byte and retry iconv. */
+ c = phase1_getc ();
+ if (c == EOF)
+ {
+ multiline_error (xstrdup (""),
+ xasprintf (_("\
+%s:%d: Incomplete multibyte sequence at end of file.\n\
+Please specify the correct source encoding through --from-code\n"),
+ real_file_name, line_number));
+ exit (EXIT_FAILURE);
+ }
+ if (c == '\n')
+ {
+ multiline_error (xstrdup (""),
+ xasprintf (_("\
+%s:%d: Incomplete multibyte sequence at end of line.\n\
+Please specify the correct source encoding through --from-code\n"),
+ real_file_name, line_number - 1));
+ exit (EXIT_FAILURE);
+ }
+ buf[bufcount++] = (unsigned char) c;
+ }
+ else
+ error (EXIT_FAILURE, errno, _("%s:%d: iconv failure"),
+ real_file_name, line_number);
+ }
+ else
+ {
+ size_t outbytes = sizeof (scratchbuf) - outsize;
+ size_t bytes = bufcount - insize;
+ ucs4_t uc;
+
+ /* We expect that one character has been produced. */
+ if (bytes == 0)
+ abort ();
+ if (outbytes == 0)
+ abort ();
+ /* Push back the unused bytes. */
+ while (insize > 0)
+ phase1_ungetc (buf[--insize]);
+ /* Convert the character from UTF-8 to UCS-4. */
+ if (u8_mbtouc (&uc, scratchbuf, outbytes) < outbytes)
+ {
+ /* scratchbuf contains an out-of-range Unicode character
+ (> 0x10ffff). */
+ multiline_error (xstrdup (""),
+ xasprintf (_("\
+%s:%d: Invalid multibyte sequence.\n\
+Please specify the source encoding through --from-code\n"),
+ real_file_name, line_number));
+ exit (EXIT_FAILURE);
+ }
+ return uc;
+ }
+ }
+#else
+ /* If we don't have iconv(), the only supported values for
+ xgettext_global_source_encoding and thus also for
+ xgettext_current_source_encoding are ASCII and UTF-8. */
+ abort ();
+#endif
+ }
+ else
+ {
+ /* Read an UTF-8 encoded character. */
+ unsigned char buf[6];
+ unsigned int count;
+ int c;
+ ucs4_t uc;
+
+ c = phase1_getc ();
+ if (c == EOF)
+ return UEOF;
+ buf[0] = c;
+ count = 1;
+
+ if (buf[0] >= 0xc0)
+ {
+ c = phase1_getc ();
+ if (c == EOF)
+ return UEOF;
+ buf[1] = c;
+ count = 2;
+ }
+
+ if (buf[0] >= 0xe0
+ && ((buf[1] ^ 0x80) < 0x40))
+ {
+ c = phase1_getc ();
+ if (c == EOF)
+ return UEOF;
+ buf[2] = c;
+ count = 3;
+ }
+
+ if (buf[0] >= 0xf0
+ && ((buf[1] ^ 0x80) < 0x40)
+ && ((buf[2] ^ 0x80) < 0x40))
+ {
+ c = phase1_getc ();
+ if (c == EOF)
+ return UEOF;
+ buf[3] = c;
+ count = 4;
+ }
+
+ if (buf[0] >= 0xf8
+ && ((buf[1] ^ 0x80) < 0x40)
+ && ((buf[2] ^ 0x80) < 0x40)
+ && ((buf[3] ^ 0x80) < 0x40))
+ {
+ c = phase1_getc ();
+ if (c == EOF)
+ return UEOF;
+ buf[4] = c;
+ count = 5;
+ }
+
+ if (buf[0] >= 0xfc
+ && ((buf[1] ^ 0x80) < 0x40)
+ && ((buf[2] ^ 0x80) < 0x40)
+ && ((buf[3] ^ 0x80) < 0x40)
+ && ((buf[4] ^ 0x80) < 0x40))
+ {
+ c = phase1_getc ();
+ if (c == EOF)
+ return UEOF;
+ buf[5] = c;
+ count = 6;
+ }
+
+ u8_mbtouc (&uc, buf, count);
+ return uc;
+ }
+}
+
+/* Supports max (9, UNINAME_MAX + 3) pushback characters. */
+static void
+phase2_ungetc (int c)
+{
+ if (c != UEOF)
+ {
+ if (phase2_pushback_length == SIZEOF (phase2_pushback))
+ abort ();
+ phase2_pushback[phase2_pushback_length++] = c;
+ }
+}
+
+
+/* ========================= Accumulating strings. ======================== */
+
+/* A string buffer type that allows appending Unicode characters.
+ Returns the entire string in UTF-8 encoding. */
+
+struct unicode_string_buffer
+{
+ /* The part of the string that has already been converted to UTF-8. */
+ char *utf8_buffer;
+ size_t utf8_buflen;
+ size_t utf8_allocated;
+};
+
+/* Initialize a 'struct unicode_string_buffer' to empty. */
+static inline void
+init_unicode_string_buffer (struct unicode_string_buffer *bp)
+{
+ bp->utf8_buffer = NULL;
+ bp->utf8_buflen = 0;
+ bp->utf8_allocated = 0;
+}
+
+/* Auxiliary function: Ensure count more bytes are available in bp->utf8. */
+static inline void
+unicode_string_buffer_append_unicode_grow (struct unicode_string_buffer *bp,
+ size_t count)
+{
+ if (bp->utf8_buflen + count > bp->utf8_allocated)
+ {
+ size_t new_allocated = 2 * bp->utf8_allocated + 10;
+ if (new_allocated < bp->utf8_buflen + count)
+ new_allocated = bp->utf8_buflen + count;
+ bp->utf8_allocated = new_allocated;
+ bp->utf8_buffer = xrealloc (bp->utf8_buffer, new_allocated);
+ }
+}
+
+/* Auxiliary function: Append a Unicode character to bp->utf8.
+ uc must be < 0x110000. */
+static inline void
+unicode_string_buffer_append_unicode (struct unicode_string_buffer *bp,
+ unsigned int uc)
+{
+ unsigned char utf8buf[6];
+ int count = u8_uctomb (utf8buf, uc, 6);
+
+ if (count < 0)
+ /* The caller should have ensured that uc is not out-of-range. */
+ abort ();
+
+ unicode_string_buffer_append_unicode_grow (bp, count);
+ memcpy (bp->utf8_buffer + bp->utf8_buflen, utf8buf, count);
+ bp->utf8_buflen += count;
+}
+
+/* Return the string buffer's contents. */
+static char *
+unicode_string_buffer_result (struct unicode_string_buffer *bp)
+{
+ /* NUL-terminate it. */
+ unicode_string_buffer_append_unicode_grow (bp, 1);
+ bp->utf8_buffer[bp->utf8_buflen] = '\0';
+ /* Return it. */
+ return bp->utf8_buffer;
+}
+
+/* Free the memory pointed to by a 'struct unicode_string_buffer'. */
+static inline void
+free_unicode_string_buffer (struct unicode_string_buffer *bp)
+{
+ free (bp->utf8_buffer);
+}
+
+
+/* ======================== Accumulating comments. ======================== */
+
+
+/* Accumulating a single comment line. */
+
+static struct unicode_string_buffer comment_buffer;
+
+static inline void
+comment_start ()
+{
+ lexical_context = lc_comment;
+ comment_buffer.utf8_buflen = 0;
+}
+
+static inline bool
+comment_at_start ()
+{
+ return (comment_buffer.utf8_buflen == 0);
+}
+
+static inline void
+comment_add (int c)
+{
+ unicode_string_buffer_append_unicode (&comment_buffer, c);
+}
+
+static inline const char *
+comment_line_end ()
+{
+ char *buffer = unicode_string_buffer_result (&comment_buffer);
+ size_t buflen = strlen (buffer);
+
+ while (buflen >= 1
+ && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t'))
+ --buflen;
+ buffer[buflen] = '\0';
+ savable_comment_add (buffer);
+ lexical_context = lc_outside;
+ return buffer;
+}
+
+
+/* These are for tracking whether comments count as immediately before
+ keyword. */
+static int last_comment_line;
+static int last_non_comment_line;
+
+
+/* ======================== Recognizing comments. ======================== */
+
+
+/* Recognizing the "coding" comment.
+ JavaScript provides to different comments: C-Style or C++ style
+*/
+
+/* Canonicalized encoding name for the current input file. */
+static const char *xgettext_current_file_source_encoding;
+
+#if HAVE_ICONV
+/* Converter from xgettext_current_file_source_encoding to UTF-8 (except from
+ ASCII or UTF-8, when this conversion is a no-op). */
+static iconv_t xgettext_current_file_source_iconv;
+#endif
+
+/* Tracking whether the current line is a continuation line or contains a
+ non-blank character. */
+static bool continuation_or_nonblank_line = false;
+
+/* Phase 3: Outside strings, replace backslash-newline with nothing and a
+ comment with nothing. */
+
+static int
+phase3_getc ()
+{
+ int c;
+
+ for (;;)
+ {
+ c = phase2_getc ();
+ if (c == '\\')
+ {
+ c = phase2_getc ();
+ if (c != '\n')
+ {
+ phase2_ungetc (c);
+ /* This shouldn't happen usually, because "A backslash is
+ illegal elsewhere on a line outside a string literal." */
+ return '\\';
+ }
+ /* Eat backslash-newline. */
+ continuation_or_nonblank_line = true;
+ }
+ else if (c == '/')
+ {
+ c = phase2_getc ();
+ if (c == '/')
+ {
+ /* Eat a comment to the end of line. */
+ last_comment_line = line_number;
+ comment_start ();
+ for (;;)
+ {
+ c = phase2_getc ();
+ if (c == UEOF || c == '\n')
+ break;
+ /* We skip all leading white space, but not EOLs. */
+ if (!(comment_at_start () && (c == ' ' || c == '\t')))
+ comment_add (c);
+ }
+ continuation_or_nonblank_line = false;
+ return c;
+ }
+ else if (c == '*')
+ {
+ /* Eat a comment to the end marker. (like this one!) */
+ last_comment_line = line_number;
+ comment_start ();
+ for (;;)
+ {
+ c = phase2_getc ();
+ if (c == UEOF)
+ break;
+ if (c == '*')
+ {
+ c = phase2_getc ();
+ if (c == '/')
+ break;
+ else
+ phase2_ungetc (c);
+ }
+ comment_add (c);
+ }
+ continuation_or_nonblank_line = false;
+ }
+ else
+ {
+ phase2_ungetc (c);
+ return '/';
+ }
+ }
+ else
+ {
+ if (c == '\n')
+ continuation_or_nonblank_line = false;
+ else if (!(c == ' ' || c == '\t' || c == '\f'))
+ continuation_or_nonblank_line = true;
+ return c;
+ }
+ }
+}
+
+/* Supports only one pushback character. */
+static void
+phase3_ungetc (int c)
+{
+ phase2_ungetc (c);
+}
+
+
+/* ========================= Accumulating strings. ======================== */
+
+/* Return value of phase7_getuc when EOF is reached. */
+#define P7_EOF (-1)
+#define P7_STRING_END (-2)
+
+/* Convert an UTF-16 or UTF-32 code point to a return value that can be
+ distinguished from a single-byte return value. */
+#define UNICODE(code) (0x100 + (code))
+
+/* Test a return value of phase7_getuc whether it designates an UTF-16 or
+ UTF-32 code point. */
+#define IS_UNICODE(p7_result) ((p7_result) >= 0x100)
+
+/* Extract the UTF-16 or UTF-32 code of a return value that satisfies
+ IS_UNICODE. */
+#define UNICODE_VALUE(p7_result) ((p7_result) - 0x100)
+
+/* A string buffer type that allows appending bytes (in the
+ xgettext_current_source_encoding) or Unicode characters.
+ Returns the entire string in UTF-8 encoding. */
+
+struct mixed_string_buffer
+{
+ /* The part of the string that has already been converted to UTF-8. */
+ char *utf8_buffer;
+ size_t utf8_buflen;
+ size_t utf8_allocated;
+ /* The first half of an UTF-16 surrogate character. */
+ unsigned short utf16_surr;
+ /* The part of the string that is still in the source encoding. */
+ char *curr_buffer;
+ size_t curr_buflen;
+ size_t curr_allocated;
+ /* The lexical context. Used only for error message purposes. */
+ lexical_context_ty lcontext;
+};
+
+/* Initialize a 'struct mixed_string_buffer' to empty. */
+static inline void
+init_mixed_string_buffer (struct mixed_string_buffer *bp, lexical_context_ty lcontext)
+{
+ bp->utf8_buffer = NULL;
+ bp->utf8_buflen = 0;
+ bp->utf8_allocated = 0;
+ bp->utf16_surr = 0;
+ bp->curr_buffer = NULL;
+ bp->curr_buflen = 0;
+ bp->curr_allocated = 0;
+ bp->lcontext = lcontext;
+}
+
+/* Auxiliary function: Append a byte to bp->curr. */
+static inline void
+mixed_string_buffer_append_byte (struct mixed_string_buffer *bp, unsigned char c)
+{
+ if (bp->curr_buflen == bp->curr_allocated)
+ {
+ bp->curr_allocated = 2 * bp->curr_allocated + 10;
+ bp->curr_buffer = xrealloc (bp->curr_buffer, bp->curr_allocated);
+ }
+ bp->curr_buffer[bp->curr_buflen++] = c;
+}
+
+/* Auxiliary function: Ensure count more bytes are available in bp->utf8. */
+static inline void
+mixed_string_buffer_append_unicode_grow (struct mixed_string_buffer *bp, size_t count)
+{
+ if (bp->utf8_buflen + count > bp->utf8_allocated)
+ {
+ size_t new_allocated = 2 * bp->utf8_allocated + 10;
+ if (new_allocated < bp->utf8_buflen + count)
+ new_allocated = bp->utf8_buflen + count;
+ bp->utf8_allocated = new_allocated;
+ bp->utf8_buffer = xrealloc (bp->utf8_buffer, new_allocated);
+ }
+}
+
+/* Auxiliary function: Append a Unicode character to bp->utf8.
+ uc must be < 0x110000. */
+static inline void
+mixed_string_buffer_append_unicode (struct mixed_string_buffer *bp, ucs4_t uc)
+{
+ unsigned char utf8buf[6];
+ int count = u8_uctomb (utf8buf, uc, 6);
+
+ if (count < 0)
+ /* The caller should have ensured that uc is not out-of-range. */
+ abort ();
+
+ mixed_string_buffer_append_unicode_grow (bp, count);
+ memcpy (bp->utf8_buffer + bp->utf8_buflen, utf8buf, count);
+ bp->utf8_buflen += count;
+}
+
+/* Auxiliary function: Flush bp->utf16_surr into bp->utf8_buffer. */
+static inline void
+mixed_string_buffer_flush_utf16_surr (struct mixed_string_buffer *bp)
+{
+ if (bp->utf16_surr != 0)
+ {
+ /* A half surrogate is invalid, therefore use U+FFFD instead. */
+ mixed_string_buffer_append_unicode (bp, 0xfffd);
+ bp->utf16_surr = 0;
+ }
+}
+
+/* Auxiliary function: Flush bp->curr_buffer into bp->utf8_buffer. */
+static inline void
+mixed_string_buffer_flush_curr_buffer (struct mixed_string_buffer *bp, int lineno)
+{
+ if (bp->curr_buflen > 0)
+ {
+ char *curr;
+ size_t count;
+
+ mixed_string_buffer_append_byte (bp, '\0');
+
+ /* Convert from the source encoding to UTF-8. */
+ curr = from_current_source_encoding (bp->curr_buffer, bp->lcontext,
+ logical_file_name, lineno);
+
+ /* Append it to bp->utf8_buffer. */
+ count = strlen (curr);
+ mixed_string_buffer_append_unicode_grow (bp, count);
+ memcpy (bp->utf8_buffer + bp->utf8_buflen, curr, count);
+ bp->utf8_buflen += count;
+
+ if (curr != bp->curr_buffer)
+ free (curr);
+ bp->curr_buflen = 0;
+ }
+}
+
+/* Append a character or Unicode character to a 'struct mixed_string_buffer'. */
+static void
+mixed_string_buffer_append (struct mixed_string_buffer *bp, int c)
+{
+ if (IS_UNICODE (c))
+ {
+ /* Append a Unicode character. */
+
+ /* Switch from multibyte character mode to Unicode character mode. */
+ mixed_string_buffer_flush_curr_buffer (bp, line_number);
+
+ /* Test whether this character and the previous one form a Unicode
+ surrogate character pair. */
+ if (bp->utf16_surr != 0
+ && (c >= UNICODE (0xdc00) && c < UNICODE (0xe000)))
+ {
+ unsigned short utf16buf[2];
+ ucs4_t uc;
+
+ utf16buf[0] = bp->utf16_surr;
+ utf16buf[1] = UNICODE_VALUE (c);
+ if (u16_mbtouc (&uc, utf16buf, 2) != 2)
+ abort ();
+
+ mixed_string_buffer_append_unicode (bp, uc);
+ bp->utf16_surr = 0;
+ }
+ else
+ {
+ mixed_string_buffer_flush_utf16_surr (bp);
+
+ if (c >= UNICODE (0xd800) && c < UNICODE (0xdc00))
+ bp->utf16_surr = UNICODE_VALUE (c);
+ else if (c >= UNICODE (0xdc00) && c < UNICODE (0xe000))
+ {
+ /* A half surrogate is invalid, therefore use U+FFFD instead. */
+ mixed_string_buffer_append_unicode (bp, 0xfffd);
+ }
+ else
+ mixed_string_buffer_append_unicode (bp, UNICODE_VALUE (c));
+ }
+ }
+ else
+ {
+ /* Append a single byte. */
+
+ /* Switch from Unicode character mode to multibyte character mode. */
+ mixed_string_buffer_flush_utf16_surr (bp);
+
+ /* When a newline is seen, convert the accumulated multibyte sequence.
+ This ensures a correct line number in the error message in case of
+ a conversion error. The "- 1" is to account for the newline. */
+ if (c == '\n')
+ mixed_string_buffer_flush_curr_buffer (bp, line_number - 1);
+
+ mixed_string_buffer_append_byte (bp, (unsigned char) c);
+ }
+}
+
+/* Return the string buffer's contents. */
+static char *
+mixed_string_buffer_result (struct mixed_string_buffer *bp)
+{
+ /* Flush all into bp->utf8_buffer. */
+ mixed_string_buffer_flush_utf16_surr (bp);
+ mixed_string_buffer_flush_curr_buffer (bp, line_number);
+ /* NUL-terminate it. */
+ mixed_string_buffer_append_unicode_grow (bp, 1);
+ bp->utf8_buffer[bp->utf8_buflen] = '\0';
+ /* Return it. */
+ return bp->utf8_buffer;
+}
+
+/* Free the memory pointed to by a 'struct mixed_string_buffer'. */
+static inline void
+free_mixed_string_buffer (struct mixed_string_buffer *bp)
+{
+ free (bp->utf8_buffer);
+ free (bp->curr_buffer);
+}
+
+
+/* ========================== Reading of tokens. ========================== */
+
+
+enum token_type_ty
+{
+ token_type_eof,
+ token_type_lparen, /* ( */
+ token_type_rparen, /* ) */
+ token_type_comma, /* , */
+ token_type_lbracket, /* [ */
+ token_type_rbracket, /* ] */
+ token_type_regexp, /* /.../ */
+ token_type_operator, /* + - * / % . < > = ~ ! | & ? : ^ */
+ token_type_string, /* "abc", 'abc' */
+ token_type_symbol, /* symbol, number */
+ token_type_other /* misc. operator */
+};
+typedef enum token_type_ty token_type_ty;
+
+typedef struct token_ty token_ty;
+struct token_ty
+{
+ token_type_ty type;
+ char *string; /* for token_type_string, token_type_symbol */
+ refcounted_string_list_ty *comment; /* for token_type_string */
+ int line_number;
+ int operator; /* for token_type_operator */
+};
+
+/* Javascript provides strings with either double or single quotes.
+ "abc" or 'abc'
+ Both may contain special sequences after backslash:
+ \\, \", \', \a\b\f\n\r\t\v
+ Special characters from ascii range are selected by on of the following
+ octal or hexadecimal sequences:
+ \oOO \xXX
+ Any unicode point can be entered using the sequence \uNNNN or \uNNNNNNNN
+ */
+
+static int
+phase7_getuc (int quote_char,
+ bool interpret_ansic, bool interpret_unicode,
+ unsigned int *backslash_counter)
+{
+ int c;
+
+ for (;;)
+ {
+ /* Use phase 2, because phase 3 elides comments. */
+ c = phase2_getc ();
+
+ if (c == UEOF)
+ return P7_EOF;
+
+ if (c == quote_char && (interpret_ansic || (*backslash_counter & 1) == 0))
+ {
+ return P7_STRING_END;
+ }
+
+ if (c == '\n')
+ {
+ phase2_ungetc (c);
+ error_with_progname = false;
+ error (0, 0, _("%s:%d: warning: unterminated string"),
+ logical_file_name, line_number);
+ error_with_progname = true;
+ return P7_STRING_END;
+ }
+
+ if (c != '\\')
+ {
+ *backslash_counter = 0;
+ return UNICODE (c);
+ }
+
+ /* Backslash handling. */
+
+ if (!interpret_ansic && !interpret_unicode)
+ {
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+
+ /* Dispatch according to the character following the backslash. */
+ c = phase2_getc ();
+ if (c == UEOF)
+ {
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+
+ if (interpret_ansic)
+ switch (c)
+ {
+ case '\n':
+ continue;
+ case '\\':
+ ++*backslash_counter;
+ return UNICODE (c);
+ case '\'': case '"':
+ *backslash_counter = 0;
+ return UNICODE (c);
+ case 'a':
+ *backslash_counter = 0;
+ return UNICODE ('\a');
+ case 'b':
+ *backslash_counter = 0;
+ return UNICODE ('\b');
+ case 'f':
+ *backslash_counter = 0;
+ return UNICODE ('\f');
+ case 'n':
+ *backslash_counter = 0;
+ return UNICODE ('\n');
+ case 'r':
+ *backslash_counter = 0;
+ return UNICODE ('\r');
+ case 't':
+ *backslash_counter = 0;
+ return UNICODE ('\t');
+ case 'v':
+ *backslash_counter = 0;
+ return UNICODE ('\v');
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7':
+ {
+ int n = c - '0';
+
+ c = phase2_getc ();
+ if (c != UEOF)
+ {
+ if (c >= '0' && c <= '7')
+ {
+ n = (n << 3) + (c - '0');
+ c = phase2_getc ();
+ if (c != UEOF)
+ {
+ if (c >= '0' && c <= '7')
+ n = (n << 3) + (c - '0');
+ else
+ phase2_ungetc (c);
+ }
+ }
+ else
+ phase2_ungetc (c);
+ }
+ *backslash_counter = 0;
+ if (interpret_unicode)
+ return UNICODE (n);
+ else
+ return (unsigned char) n;
+ }
+ case 'x':
+ {
+ int c1 = phase2_getc ();
+ int n1;
+
+ if (c1 >= '0' && c1 <= '9')
+ n1 = c1 - '0';
+ else if (c1 >= 'A' && c1 <= 'F')
+ n1 = c1 - 'A' + 10;
+ else if (c1 >= 'a' && c1 <= 'f')
+ n1 = c1 - 'a' + 10;
+ else
+ n1 = -1;
+
+ if (n1 >= 0)
+ {
+ int c2 = phase2_getc ();
+ int n2;
+
+ if (c2 >= '0' && c2 <= '9')
+ n2 = c2 - '0';
+ else if (c2 >= 'A' && c2 <= 'F')
+ n2 = c2 - 'A' + 10;
+ else if (c2 >= 'a' && c2 <= 'f')
+ n2 = c2 - 'a' + 10;
+ else
+ n2 = -1;
+
+ if (n2 >= 0)
+ {
+ int n = (n1 << 4) + n2;
+ *backslash_counter = 0;
+ if (interpret_unicode)
+ return UNICODE (n);
+ else
+ return (unsigned char) n;
+ }
+
+ phase2_ungetc (c2);
+ }
+ phase2_ungetc (c1);
+ phase2_ungetc (c);
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+ }
+
+ if (interpret_unicode)
+ {
+ if (c == 'u')
+ {
+ unsigned char buf[4];
+ unsigned int n = 0;
+ int i;
+
+ for (i = 0; i < 4; i++)
+ {
+ int c1 = phase2_getc ();
+
+ if (c1 >= '0' && c1 <= '9')
+ n = (n << 4) + (c1 - '0');
+ else if (c1 >= 'A' && c1 <= 'F')
+ n = (n << 4) + (c1 - 'A' + 10);
+ else if (c1 >= 'a' && c1 <= 'f')
+ n = (n << 4) + (c1 - 'a' + 10);
+ else
+ {
+ phase2_ungetc (c1);
+ while (--i >= 0)
+ phase2_ungetc (buf[i]);
+ phase2_ungetc (c);
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+
+ buf[i] = c1;
+ }
+ *backslash_counter = 0;
+ return UNICODE (n);
+ }
+
+ if (interpret_ansic)
+ {
+ if (c == 'U')
+ {
+ unsigned char buf[8];
+ unsigned int n = 0;
+ int i;
+
+ for (i = 0; i < 8; i++)
+ {
+ int c1 = phase2_getc ();
+
+ if (c1 >= '0' && c1 <= '9')
+ n = (n << 4) + (c1 - '0');
+ else if (c1 >= 'A' && c1 <= 'F')
+ n = (n << 4) + (c1 - 'A' + 10);
+ else if (c1 >= 'a' && c1 <= 'f')
+ n = (n << 4) + (c1 - 'a' + 10);
+ else
+ {
+ phase2_ungetc (c1);
+ while (--i >= 0)
+ phase2_ungetc (buf[i]);
+ phase2_ungetc (c);
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+
+ buf[i] = c1;
+ }
+ if (n < 0x110000)
+ {
+ *backslash_counter = 0;
+ return UNICODE (n);
+ }
+
+ error_with_progname = false;
+ error (0, 0, _("%s:%d: warning: invalid Unicode character"),
+ logical_file_name, line_number);
+ error_with_progname = true;
+
+ while (--i >= 0)
+ phase2_ungetc (buf[i]);
+ phase2_ungetc (c);
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+
+ if (c == 'N')
+ {
+ int c1 = phase2_getc ();
+ if (c1 == '{')
+ {
+ unsigned char buf[UNINAME_MAX + 1];
+ int i;
+ unsigned int n;
+
+ for (i = 0; i < UNINAME_MAX; i++)
+ {
+ int c2 = phase2_getc ();
+ if (!(c2 >= ' ' && c2 <= '~'))
+ {
+ phase2_ungetc (c2);
+ while (--i >= 0)
+ phase2_ungetc (buf[i]);
+ phase2_ungetc (c1);
+ phase2_ungetc (c);
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+ if (c2 == '}')
+ break;
+ buf[i] = c2;
+ }
+ buf[i] = '\0';
+
+ n = unicode_name_character ((char *) buf);
+ if (n != UNINAME_INVALID)
+ {
+ *backslash_counter = 0;
+ return UNICODE (n);
+ }
+
+ phase2_ungetc ('}');
+ while (--i >= 0)
+ phase2_ungetc (buf[i]);
+ }
+ phase2_ungetc (c1);
+ phase2_ungetc (c);
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+ }
+ }
+
+ phase2_ungetc (c);
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+}
+
+
+/* Combine characters into tokens. Discard whitespace except newlines at
+ the end of logical lines. */
+
+/* Number of pending open parentheses/braces/brackets. */
+static int open_pbb;
+
+static token_ty phase5_pushback[2];
+static int phase5_pushback_length;
+
+static token_type_ty last_token_type = token_type_other;
+
+static void
+phase5_scan_regexp ()
+{
+ int c;
+ /* scan for end of RegExp literal ('/') */
+ for (;;)
+ {
+ c = phase2_getc (); /* must use phase2 as there can't be comments */
+ if (c == '/')
+ break;
+ switch (c)
+ {
+ case UEOF:
+ error_with_progname = false;
+ error (0, 0, _("%s:%d: warning: RegExp literal terminated too early"),
+ logical_file_name, line_number);
+ error_with_progname = true;
+ return;
+
+ case '\n':
+ error_with_progname = false;
+ error (0, 0, _("%s:%d: warning: RegExp literal contains newline"),
+ logical_file_name, line_number);
+ error_with_progname = true;
+ return;
+
+ case '\\':
+ {
+ int c2 = phase2_getc ();
+ if (c2 == UEOF)
+ {
+ error_with_progname = false;
+ error (0, 0, _("%s:%d: warning: RegExp literal terminated too early"),
+ logical_file_name, line_number);
+ error_with_progname = true;
+ return;
+ }
+ }
+ break;
+
+ default:
+ continue;
+ }
+ }
+ /* scan for modifier flags (ECMA-262 5th section 15.10.4.1) */
+ c = phase2_getc ();
+ if (c == 'g' || c == 'i' || c == 'm')
+ {
+ return;
+ }
+ else
+ {
+ phase2_ungetc (c);
+ }
+}
+
+static void
+phase5_get (token_ty *tp)
+{
+ int c;
+
+ if (phase5_pushback_length)
+ {
+ *tp = phase5_pushback[--phase5_pushback_length];
+ last_token_type = tp->type;
+ return;
+ }
+
+ for (;;)
+ {
+ tp->line_number = line_number;
+ c = phase3_getc ();
+
+ switch (c)
+ {
+ case UEOF:
+ tp->type = last_token_type = token_type_eof;
+ return;
+
+ case ' ':
+ case '\t':
+ case '\f':
+ /* Ignore whitespace and comments. */
+ continue;
+
+ case '\n':
+ if (last_non_comment_line > last_comment_line)
+ savable_comment_reset ();
+ /* Ignore newline if and only if it is used for implicit line
+ joining. */
+ if (open_pbb > 0)
+ continue;
+ tp->type = last_token_type = token_type_other;
+ return;
+ }
+
+ last_non_comment_line = tp->line_number;
+
+ switch (c)
+ {
+ case '.':
+ {
+ int c1 = phase3_getc ();
+ phase3_ungetc (c1);
+ if (!(c1 >= '0' && c1 <= '9'))
+ {
+
+ tp->type = last_token_type = token_type_other;
+ return;
+ }
+ }
+ /* FALLTHROUGH */
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
+ case 'M': case 'N': case 'O': case 'P': case 'Q':
+ case 'S': case 'T': case 'V': case 'W': case 'X':
+ case 'Y': case 'Z':
+ case '_':
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
+ case 'm': case 'n': case 'o': case 'p': case 'q':
+ case 's': case 't': case 'v': case 'w': case 'x':
+ case 'y': case 'z':
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ symbol:
+ /* Symbol, or part of a number. */
+ {
+ static char *buffer;
+ static int bufmax;
+ int bufpos;
+
+ bufpos = 0;
+ for (;;)
+ {
+ if (bufpos >= bufmax)
+ {
+ bufmax = 2 * bufmax + 10;
+ buffer = xrealloc (buffer, bufmax);
+ }
+ buffer[bufpos++] = c;
+ c = phase3_getc ();
+ switch (c)
+ {
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
+ case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
+ case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
+ case 'Y': case 'Z':
+ case '_':
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
+ case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
+ case 's': case 't': case 'u': case 'v': case 'w': case 'x':
+ case 'y': case 'z':
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ continue;
+ default:
+ phase3_ungetc (c);
+ break;
+ }
+ break;
+ }
+ if (bufpos >= bufmax)
+ {
+ bufmax = 2 * bufmax + 10;
+ buffer = xrealloc (buffer, bufmax);
+ }
+ buffer[bufpos] = '\0';
+ tp->string = xstrdup (buffer);
+ tp->type = last_token_type = token_type_symbol;
+ return;
+ }
+
+ /* Strings. */
+ {
+ struct mixed_string_buffer literal;
+ int quote_char;
+ bool interpret_ansic;
+ bool interpret_unicode;
+ unsigned int backslash_counter;
+
+ case '"': case '\'':
+ quote_char = c;
+ interpret_ansic = true;
+ interpret_unicode = false;
+ string:
+ lexical_context = lc_string;
+ backslash_counter = 0;
+ /* Start accumulating the string. */
+ init_mixed_string_buffer (&literal, lc_string);
+ for (;;)
+ {
+ int uc = phase7_getuc (quote_char, interpret_ansic,
+ interpret_unicode, &backslash_counter);
+
+ if (uc == P7_EOF || uc == P7_STRING_END)
+ break;
+
+ if (IS_UNICODE (uc))
+ assert (UNICODE_VALUE (uc) >= 0
+ && UNICODE_VALUE (uc) < 0x110000);
+
+ mixed_string_buffer_append (&literal, uc);
+ }
+ tp->string = xstrdup (mixed_string_buffer_result (&literal));
+ free_mixed_string_buffer (&literal);
+ tp->comment = add_reference (savable_comment);
+ lexical_context = lc_outside;
+ tp->type = last_token_type = token_type_string;
+ return;
+ }
+
+ /* Identify operators. The multiple character ones are simply ignored
+ * as they are recognized here and are otherwise not relevant. */
+ /* FALLTHROUGH */
+ case '+': case '-': case '*': /* '/' is not listed here! */
+ case '%': case '<': case '>': case '=':
+ case '~': case '!': case '|': case '&': case '^':
+ case '?': case ':':
+ tp->operator = c;
+ tp->type = last_token_type = token_type_operator;
+ return;
+
+ case '/':
+ /* Either a division operator or the start of a RegExp literal.
+ * If the '/' token is spotted after a symbol it's a division,
+ * otherwise it's a regex */
+ if (last_token_type == token_type_symbol ||
+ last_token_type == token_type_rparen ||
+ last_token_type == token_type_rbracket)
+ {
+ tp->operator = '/';
+ tp->type = last_token_type = token_type_operator;
+ return;
+ }
+ else
+ {
+ phase5_scan_regexp ();
+ tp->type = last_token_type = token_type_other;
+ return;
+ }
+
+ case '(':
+ open_pbb++;
+ tp->type = last_token_type = token_type_lparen;
+ return;
+
+ case ')':
+ if (open_pbb > 0)
+ open_pbb--;
+ tp->type = last_token_type = token_type_rparen;
+ return;
+
+ case ',':
+ tp->type = last_token_type = token_type_comma;
+ return;
+
+ case '[': case '{':
+ open_pbb++;
+ tp->type = last_token_type = (c == '[' ? token_type_lbracket : token_type_other);
+ return;
+
+ case ']': case '}':
+ if (open_pbb > 0)
+ open_pbb--;
+ tp->type = last_token_type = (c == ']' ? token_type_rbracket : token_type_other);
+ return;
+
+ default:
+ /* We could carefully recognize each of the 2 and 3 character
+ operators, but it is not necessary, as we only need to recognize
+ gettext invocations. Don't bother. */
+ tp->type = last_token_type = token_type_other;
+ return;
+ }
+ }
+}
+
+/* Supports only one pushback token. */
+static void
+phase5_unget (token_ty *tp)
+{
+ if (tp->type != token_type_eof)
+ {
+ if (phase5_pushback_length == SIZEOF (phase5_pushback))
+ abort ();
+ phase5_pushback[phase5_pushback_length++] = *tp;
+ }
+}
+
+
+/* Combine adjacent strings to form a single string. Note that the end
+ of a logical line appears as a token of its own, therefore strings that
+ belong to different logical lines will not be concatenated. */
+
+static void
+x_javascript_lex (token_ty *tp)
+{
+ phase5_get (tp);
+ if (tp->type != token_type_string)
+ return;
+ for (;;)
+ {
+ token_ty tmp;
+ size_t len;
+
+ phase5_get (&tmp);
+ if (tmp.type == token_type_operator && tmp.operator == '+')
+ {
+ /* allow concatenation of strings using the plus '+' operator */
+ token_ty tmp2;
+ phase5_get (&tmp2);
+ if (tmp2.type != token_type_string)
+ {
+ phase5_unget (&tmp2);
+ phase5_unget (&tmp);
+ return;
+ }
+ memcpy(&tmp, &tmp2, sizeof(tmp));
+ }
+ else if (tmp.type != token_type_string)
+ {
+ phase5_unget (&tmp);
+ return;
+ }
+ len = strlen (tp->string);
+ tp->string = xrealloc (tp->string, len + strlen (tmp.string) + 1);
+ strcpy (tp->string + len, tmp.string);
+ free (tmp.string);
+ }
+}
+
+
+/* ========================= Extracting strings. ========================== */
+
+
+/* Context lookup table. */
+static flag_context_list_table_ty *flag_context_list_table;
+
+
+/* The file is broken into tokens. Scan the token stream, looking for
+ a keyword, followed by a left paren, followed by a string. When we
+ see this sequence, we have something to remember. We assume we are
+ looking at a valid Javascript program, and leave the complaints about
+ the grammar to the compiler.
+
+ Normal handling: Look for
+ keyword ( ... msgid ... )
+ Plural handling: Look for
+ keyword ( ... msgid ... msgid_plural ... )
+
+ We use recursion because the arguments before msgid or between msgid
+ and msgid_plural can contain subexpressions of the same form. */
+
+
+/* Extract messages until the next balanced closing parenthesis or bracket.
+ Extracted messages are added to MLP.
+ DELIM can be either token_type_rparen or token_type_rbracket, or
+ token_type_eof to accept both.
+ Return true upon eof, false upon closing parenthesis or bracket. */
+static bool
+extract_balanced (message_list_ty *mlp,
+ token_type_ty delim,
+ flag_context_ty outer_context,
+ flag_context_list_iterator_ty context_iter,
+ struct arglist_parser *argparser)
+{
+ /* Current argument number. */
+ int arg = 1;
+ /* 0 when no keyword has been seen. 1 right after a keyword is seen. */
+ int state;
+ /* Parameters of the keyword just seen. Defined only in state 1. */
+ const struct callshapes *next_shapes = NULL;
+ /* Context iterator that will be used if the next token is a '('. */
+ flag_context_list_iterator_ty next_context_iter =
+ passthrough_context_list_iterator;
+ /* Current context. */
+ flag_context_ty inner_context =
+ inherited_context (outer_context,
+ flag_context_list_iterator_advance (&context_iter));
+
+ /* Start state is 0. */
+ state = 0;
+
+ for (;;)
+ {
+ token_ty token;
+
+ x_javascript_lex (&token);
+ switch (token.type)
+ {
+ case token_type_symbol:
+ {
+ void *keyword_value;
+
+ if (hash_find_entry (&keywords, token.string, strlen (token.string),
+ &keyword_value)
+ == 0)
+ {
+ next_shapes = (const struct callshapes *) keyword_value;
+ state = 1;
+ }
+ else
+ state = 0;
+ }
+ next_context_iter =
+ flag_context_list_iterator (
+ flag_context_list_table_lookup (
+ flag_context_list_table,
+ token.string, strlen (token.string)));
+ free (token.string);
+ continue;
+
+ case token_type_lparen:
+ if (extract_balanced (mlp, token_type_rparen,
+ inner_context, next_context_iter,
+ arglist_parser_alloc (mlp,
+ state ? next_shapes : NULL)))
+ {
+ xgettext_current_source_encoding = po_charset_utf8;
+ arglist_parser_done (argparser, arg);
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding;
+ return true;
+ }
+ next_context_iter = null_context_list_iterator;
+ state = 0;
+ continue;
+
+ case token_type_rparen:
+ if (delim == token_type_rparen || delim == token_type_eof)
+ {
+ xgettext_current_source_encoding = po_charset_utf8;
+ arglist_parser_done (argparser, arg);
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding;
+ return false;
+ }
+ next_context_iter = null_context_list_iterator;
+ state = 0;
+ continue;
+
+ case token_type_comma:
+ arg++;
+ inner_context =
+ inherited_context (outer_context,
+ flag_context_list_iterator_advance (
+ &context_iter));
+ next_context_iter = passthrough_context_list_iterator;
+ state = 0;
+ continue;
+
+ case token_type_lbracket:
+ if (extract_balanced (mlp, token_type_rbracket,
+ null_context, null_context_list_iterator,
+ arglist_parser_alloc (mlp, NULL)))
+ {
+ xgettext_current_source_encoding = po_charset_utf8;
+ arglist_parser_done (argparser, arg);
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding;
+ return true;
+ }
+ next_context_iter = null_context_list_iterator;
+ state = 0;
+ continue;
+
+ case token_type_rbracket:
+ if (delim == token_type_rbracket || delim == token_type_eof)
+ {
+ xgettext_current_source_encoding = po_charset_utf8;
+ arglist_parser_done (argparser, arg);
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding;
+ return false;
+ }
+ next_context_iter = null_context_list_iterator;
+ state = 0;
+ continue;
+
+ case token_type_string:
+ {
+ lex_pos_ty pos;
+ pos.file_name = logical_file_name;
+ pos.line_number = token.line_number;
+
+ xgettext_current_source_encoding = po_charset_utf8;
+ if (extract_all)
+ remember_a_message (mlp, NULL, token.string, inner_context,
+ &pos, NULL, token.comment);
+ else
+ arglist_parser_remember (argparser, arg, token.string,
+ inner_context,
+ pos.file_name, pos.line_number,
+ token.comment);
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding;
+ }
+ drop_reference (token.comment);
+ next_context_iter = null_context_list_iterator;
+ state = 0;
+ continue;
+
+ case token_type_eof:
+ xgettext_current_source_encoding = po_charset_utf8;
+ arglist_parser_done (argparser, arg);
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding;
+ return true;
+
+ /* FALLTHROUGH */
+ case token_type_regexp:
+ case token_type_operator:
+ case token_type_other:
+ next_context_iter = null_context_list_iterator;
+ state = 0;
+ continue;
+
+ default:
+ abort ();
+ }
+ }
+}
+
+
+void
+extract_javascript (FILE *f,
+ const char *real_filename, const char *logical_filename,
+ flag_context_list_table_ty *flag_table,
+ msgdomain_list_ty *mdlp)
+{
+ message_list_ty *mlp = mdlp->item[0]->messages;
+
+ fp = f;
+ real_file_name = real_filename;
+ logical_file_name = xstrdup (logical_filename);
+ line_number = 1;
+
+ lexical_context = lc_outside;
+
+ last_comment_line = -1;
+ last_non_comment_line = -1;
+
+ xgettext_current_file_source_encoding = xgettext_global_source_encoding;
+#if HAVE_ICONV
+ xgettext_current_file_source_iconv = xgettext_global_source_iconv;
+#endif
+
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding;
+#if HAVE_ICONV
+ xgettext_current_source_iconv = xgettext_current_file_source_iconv;
+#endif
+
+ continuation_or_nonblank_line = false;
+
+ open_pbb = 0;
+
+ flag_context_list_table = flag_table;
+
+ init_keywords ();
+
+ /* Eat tokens until eof is seen. When extract_balanced returns
+ due to an unbalanced closing parenthesis, just restart it. */
+ while (!extract_balanced (mlp, token_type_eof,
+ null_context, null_context_list_iterator,
+ arglist_parser_alloc (mlp, NULL)))
+ ;
+
+ fp = NULL;
+ real_file_name = NULL;
+ logical_file_name = NULL;
+ line_number = 0;
+}
diff --git c/gettext-tools/src/x-javascript.h w/gettext-tools/src/x-javascript.h
new file mode 100644
index 0000000..d668136
--- /dev/null
+++ w/gettext-tools/src/x-javascript.h
@@ -0,0 +1,52 @@
+/* xgettext Python backend.
+ Copyright (C) 2002-2003, 2006 Free Software Foundation, Inc.
+ This file was written by Andreas Stricker <[email protected]>, 2010.
+ It's based on x-python from Bruno Haible.
+
+ 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 <http://www.gnu.org/licenses/>. */
+
+
+#include <stdio.h>
+
+#include "message.h"
+#include "xgettext.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define EXTENSIONS_JAVASCRIPT \
+ { "js", "JavaScript" }, \
+
+#define SCANNERS_JAVASCRIPT \
+ { "JavaScript", extract_javascript, \
+ &flag_table_javascript, &formatstring_javascript, NULL }, \
+
+/* Scan a Python file and add its translatable strings to mdlp. */
+extern void extract_javascript (FILE *fp, const char *real_filename,
+ const char *logical_filename,
+ flag_context_list_table_ty *flag_table,
+ msgdomain_list_ty *mdlp);
+
+extern void x_javascript_keyword (const char *keyword);
+extern void x_javascript_extract_all (void);
+
+extern void init_flag_table_javascript (void);
+
+
+#ifdef __cplusplus
+}
+#endif
diff --git c/gettext-tools/src/xgettext.c w/gettext-tools/src/xgettext.c
index 919f30b..fc2d127 100644
--- c/gettext-tools/src/xgettext.c
+++ w/gettext-tools/src/xgettext.c
@@ -82,6 +82,7 @@
#include "x-scheme.h"
#include "x-smalltalk.h"
#include "x-java.h"
+#include "x-javascript.h"
#include "x-properties.h"
#include "x-csharp.h"
#include "x-awk.h"
@@ -154,6 +155,7 @@ static flag_context_list_table_ty flag_table_elisp;
static flag_context_list_table_ty flag_table_librep;
static flag_context_list_table_ty flag_table_scheme;
static flag_context_list_table_ty flag_table_java;
+static flag_context_list_table_ty flag_table_javascript;
static flag_context_list_table_ty flag_table_csharp;
static flag_context_list_table_ty flag_table_awk;
static flag_context_list_table_ty flag_table_ycp;
@@ -325,6 +327,7 @@ main (int argc, char *argv[])
init_flag_table_librep ();
init_flag_table_scheme ();
init_flag_table_java ();
+ init_flag_table_javascript ();
init_flag_table_csharp ();
init_flag_table_awk ();
init_flag_table_ycp ();
@@ -349,6 +352,7 @@ main (int argc, char *argv[])
x_librep_extract_all ();
x_scheme_extract_all ();
x_java_extract_all ();
+ x_javascript_extract_all ();
x_csharp_extract_all ();
x_awk_extract_all ();
x_tcl_extract_all ();
@@ -426,6 +430,7 @@ main (int argc, char *argv[])
x_librep_keyword (optarg);
x_scheme_keyword (optarg);
x_java_keyword (optarg);
+ x_javascript_keyword (optarg);
x_csharp_keyword (optarg);
x_awk_keyword (optarg);
x_tcl_keyword (optarg);
@@ -855,8 +860,9 @@ Choice of input file language:\n"));
-L, --language=NAME recognise the specified language\n\
(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, Glade)\n"));
+ JavaProperties, JavaScript, C#, awk, YCP, Tcl,\n\
+ Perl, PHP, GCC-source, NXStringTable, RST,\n\
+ Glade)\n"));
printf (_("\
-C, --c++ shorthand for --language=C++\n"));
printf (_("\
@@ -1700,6 +1706,11 @@ xgettext_record_flag (const char *optionstring)
name_start, name_end,
argnum, value, pass);
break;
+ case format_javascript:
+ flag_context_list_table_insert (&flag_table_javascript, 0,
+ name_start, name_end,
+ argnum, value, pass);
+ break;
case format_csharp:
flag_context_list_table_insert (&flag_table_csharp, 0,
name_start, name_end,
@@ -3171,6 +3182,7 @@ language_to_extractor (const char *name)
SCANNERS_SCHEME
SCANNERS_SMALLTALK
SCANNERS_JAVA
+ SCANNERS_JAVASCRIPT
SCANNERS_PROPERTIES
SCANNERS_CSHARP
SCANNERS_AWK
@@ -3254,6 +3266,7 @@ extension_to_language (const char *extension)
EXTENSIONS_SCHEME
EXTENSIONS_SMALLTALK
EXTENSIONS_JAVA
+ EXTENSIONS_JAVASCRIPT
EXTENSIONS_PROPERTIES
EXTENSIONS_CSHARP
EXTENSIONS_AWK
diff --git c/gettext-tools/tests/Makefile.am w/gettext-tools/tests/Makefile.am
index 0042a3b..2a78fc9 100644
--- c/gettext-tools/tests/Makefile.am
+++ w/gettext-tools/tests/Makefile.am
@@ -82,6 +82,7 @@ TESTS = gettext-1 gettext-2 gettext-3 gettext-4 gettext-5 gettext-6 gettext-7 \
xgettext-glade-1 xgettext-glade-2 xgettext-glade-3 xgettext-glade-4 \
xgettext-java-1 xgettext-java-2 xgettext-java-3 xgettext-java-4 \
xgettext-java-5 xgettext-java-6 xgettext-java-7 \
+ xgettext-javascript-1 xgettext-javascript-2 xgettext-javascript-3 \
xgettext-librep-1 xgettext-librep-2 \
xgettext-lisp-1 xgettext-lisp-2 \
xgettext-objc-1 xgettext-objc-2 \
diff --git c/gettext-tools/tests/Makefile.in w/gettext-tools/tests/Makefile.in
index 017ca1d..b2d07c0 100644
--- c/gettext-tools/tests/Makefile.in
+++ w/gettext-tools/tests/Makefile.in
@@ -1337,6 +1337,7 @@ TESTS = gettext-1 gettext-2 gettext-3 gettext-4 gettext-5 gettext-6 gettext-7 \
xgettext-glade-1 xgettext-glade-2 xgettext-glade-3 xgettext-glade-4 \
xgettext-java-1 xgettext-java-2 xgettext-java-3 xgettext-java-4 \
xgettext-java-5 xgettext-java-6 xgettext-java-7 \
+ xgettext-javascript-1 xgettext-javascript-2 xgettext-javascript-3 \
xgettext-librep-1 xgettext-librep-2 \
xgettext-lisp-1 xgettext-lisp-2 \
xgettext-objc-1 xgettext-objc-2 \
diff --git c/gettext-tools/tests/xgettext-javascript-1 w/gettext-tools/tests/xgettext-javascript-1
new file mode 100644
index 0000000..e99523d
--- /dev/null
+++ w/gettext-tools/tests/xgettext-javascript-1
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+# Test of Javascript support.
+
+tmpfiles=""
+trap 'rm -fr $tmpfiles' 1 2 3 15
+
+tmpfiles="$tmpfiles xg-js-1.js"
+cat <<\EOF > xg-js-1.js
+var s1 = "Simple string, no gettext needed",
+ s2 = _("Extract this first string");
+function foo(a) {
+ var s3 = "Prefix _(" + _("Extract this second string") + ") Postfix";
+}
+if (document.getElementsById("foo")[0].innerHTML == _("Extract this thirth string")) {
+ /* _("This is a comment and must not be extracted!") */
+}
+EOF
+
+tmpfiles="$tmpfiles xg-js-1.err xg-js-1.tmp xg-js-1.pot"
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --add-comments --no-location -o xg-js-1.tmp xg-js-1.js 2>xg-js-1.err
+test $? = 0 || { cat xg-js-1.err; rm -fr $tmpfiles; exit 1; }
+# Don't simplify this to "grep ... < xg-js-1.tmp", otherwise OpenBSD 4.0 grep
+# only outputs "Binary file (standard input) matches".
+cat xg-js-1.tmp | grep -v 'POT-Creation-Date' | LC_ALL=C tr -d '\r' > xg-js-1.pot
+
+tmpfiles="$tmpfiles xg-js-1.ok"
+cat <<\EOF > xg-js-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 <[email protected]>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "Extract this first string"
+msgstr ""
+
+msgid "Extract this second string"
+msgstr ""
+
+msgid "Extract this thirth string"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-js-1.ok xg-js-1.pot
+result=$?
+
+rm -fr $tmpfiles
+
+exit $result
diff --git c/gettext-tools/tests/xgettext-javascript-2 w/gettext-tools/tests/xgettext-javascript-2
new file mode 100644
index 0000000..9766646
--- /dev/null
+++ w/gettext-tools/tests/xgettext-javascript-2
@@ -0,0 +1,91 @@
+#!/bin/sh
+
+# Test of Javascript support.
+# Playing with regex and division operator
+
+tmpfiles=""
+trap 'rm -fr $tmpfiles' 1 2 3 15
+
+tmpfiles="$tmpfiles xg-js-2.js"
+cat <<\EOF > xg-js-2.js
+// RegExp literals containing string quotes must not desync the parser
+var d = 1 / 2 / 4;
+var s = " x " + /^\d/.match("0815").replace(/[a-z]/g, '@');
+var s1 = /"/.match(_("RegExp test string #1"));
+var s2 = /'/.match(_("RegExp test string #2"));
+var s3 = /['a-b]/.match(_('RegExp test string #3'));
+var s4 = /["a-b]/.match(_('RegExp test string #4'));
+var s5 = /[a-b']/.match(_('RegExp test string #5'));
+var s6 = /[a-b"]/.match(_('RegExp test string #6'));
+var c = 35 / 2 / 8 + _("RegExp test string #7").length / 32.0;
+var sizestr = Math.round(size/1024*factor)/factor+_("RegExp test string #8");
+var cssClassType = attr.type.replace(/^.*\//, _('RegExp test string #9')).replace(/\./g, '-');
+var lookup = lookuptable[idx]/factor+_("RegExp test string #10");
+EOF
+
+tmpfiles="$tmpfiles xg-js-2.err xg-js-2.tmp xg-js-2.pot"
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --add-comments --no-location -o xg-js-2.tmp xg-js-2.js 2>xg-js-2.err
+test $? = 0 || { cat xg-js-2.err; rm -fr $tmpfiles; exit 1; }
+# Don't simplify this to "grep ... < xg-js-2.tmp", otherwise OpenBSD 4.0 grep
+# only outputs "Binary file (standard input) matches".
+cat xg-js-2.tmp | grep -v 'POT-Creation-Date' | LC_ALL=C tr -d '\r' > xg-js-2.pot
+
+tmpfiles="$tmpfiles xg-js-2.ok"
+cat <<\EOF > xg-js-2.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 <[email protected]>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "RegExp test string #1"
+msgstr ""
+
+msgid "RegExp test string #2"
+msgstr ""
+
+msgid "RegExp test string #3"
+msgstr ""
+
+msgid "RegExp test string #4"
+msgstr ""
+
+msgid "RegExp test string #5"
+msgstr ""
+
+msgid "RegExp test string #6"
+msgstr ""
+
+msgid "RegExp test string #7"
+msgstr ""
+
+msgid "RegExp test string #8"
+msgstr ""
+
+msgid "RegExp test string #9"
+msgstr ""
+
+msgid "RegExp test string #10"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-js-2.ok xg-js-2.pot
+result=$?
+
+rm -fr $tmpfiles
+
+exit $result
diff --git c/gettext-tools/tests/xgettext-javascript-3 w/gettext-tools/tests/xgettext-javascript-3
new file mode 100644
index 0000000..44e5366
--- /dev/null
+++ w/gettext-tools/tests/xgettext-javascript-3
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+# Test of Javascript support.
+# Playing with concatenation of string literals withing the gettext function
+
+tmpfiles=""
+trap 'rm -fr $tmpfiles' 1 2 3 15
+
+tmpfiles="$tmpfiles xg-js-2.js"
+cat <<\EOF > xg-js-2.js
+// The usual way to concatenate strings is the plus '+' sign
+var s1 = _("Concatenation #1 " "- String part added");
+var s2 = _('Concatenation #2 ' + '- String part added');
+var s3 = _('Concatenation #3 '
+ '- String part added');
+var s4 = _('Concatenation #4 ' +
+ '- String part added');
+var s5 = _("This" + " whole "
+ + "string" +
+ ' should' + " be " + 'extracted');
+EOF
+
+tmpfiles="$tmpfiles xg-js-2.err xg-js-2.tmp xg-js-2.pot"
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --add-comments --no-location -o xg-js-2.tmp xg-js-2.js 2>xg-js-2.err
+test $? = 0 || { cat xg-js-2.err; rm -fr $tmpfiles; exit 1; }
+# Don't simplify this to "grep ... < xg-js-2.tmp", otherwise OpenBSD 4.0 grep
+# only outputs "Binary file (standard input) matches".
+cat xg-js-2.tmp | grep -v 'POT-Creation-Date' | LC_ALL=C tr -d '\r' > xg-js-2.pot
+
+tmpfiles="$tmpfiles xg-js-2.ok"
+cat <<\EOF > xg-js-2.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 <[email protected]>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "Concatenation #1 - String part added"
+msgstr ""
+
+msgid "Concatenation #2 - String part added"
+msgstr ""
+
+msgid "Concatenation #3 - String part added"
+msgstr ""
+
+msgid "Concatenation #4 - String part added"
+msgstr ""
+
+msgid "This whole string should be extracted"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-js-2.ok xg-js-2.pot
+result=$?
+
+rm -fr $tmpfiles
+
+exit $result
diff --git c/gettext-tools/woe32dll/gettextsrc-exports.c w/gettext-tools/woe32dll/gettextsrc-exports.c
index b1c4a53..f150e36 100644
--- c/gettext-tools/woe32dll/gettextsrc-exports.c
+++ w/gettext-tools/woe32dll/gettextsrc-exports.c
@@ -29,6 +29,7 @@ VARIABLE(formatstring_elisp)
VARIABLE(formatstring_gcc_internal)
VARIABLE(formatstring_gfc_internal)
VARIABLE(formatstring_java)
+VARIABLE(formatstring_javascript)
VARIABLE(formatstring_kde)
VARIABLE(formatstring_librep)
VARIABLE(formatstring_lisp)
diff --git c/gettext-tools/libgettextpo/Makefile.am i/gettext-tools/libgettextpo/Makefile.am
index cf4a928..cee9418 100644
--- c/gettext-tools/libgettextpo/Makefile.am
+++ i/gettext-tools/libgettextpo/Makefile.am
@@ -70,6 +70,7 @@ libgettextpo_la_AUXSOURCES = \
../src/format-librep.c \
../src/format-scheme.c \
../src/format-java.c \
+ ../src/format-javascript.c \
../src/format-csharp.c \
../src/format-awk.c \
../src/format-pascal.c \
diff --git c/gettext-tools/libgettextpo/Makefile.in i/gettext-tools/libgettextpo/Makefile.in
index 20a60b8..589a373 100644
--- c/gettext-tools/libgettextpo/Makefile.in
+++ i/gettext-tools/libgettextpo/Makefile.in
@@ -387,7 +387,7 @@ am__libgettextpo_la_SOURCES_DIST = gettext-po.c ../src/str-list.c \
../src/plural-table.c ../src/format-c.c ../src/format-sh.c \
../src/format-python.c ../src/format-lisp.c \
../src/format-elisp.c ../src/format-librep.c \
- ../src/format-scheme.c ../src/format-java.c \
+ ../src/format-scheme.c ../src/format-java.c ../src/format-javascript.c \
../src/format-csharp.c ../src/format-awk.c \
../src/format-pascal.c ../src/format-ycp.c ../src/format-tcl.c \
../src/format-perl.c ../src/format-perl-brace.c \
@@ -403,7 +403,7 @@ am__objects_1 = str-list.lo dir-list.lo message.lo msgl-ascii.lo \
read-po.lo read-catalog-abstract.lo read-catalog.lo \
plural-table.lo format-c.lo format-sh.lo format-python.lo \
format-lisp.lo format-elisp.lo format-librep.lo \
- format-scheme.lo format-java.lo format-csharp.lo format-awk.lo \
+ format-scheme.lo format-java.lo format-javascript.lo format-csharp.lo format-awk.lo \
format-pascal.lo format-ycp.lo format-tcl.lo format-perl.lo \
format-perl-brace.lo format-php.lo format-gcc-internal.lo \
format-gfc-internal.lo format-qt.lo format-qt-plural.lo \
@@ -1729,6 +1729,7 @@ libgettextpo_la_AUXSOURCES = \
../src/format-librep.c \
../src/format-scheme.c \
../src/format-java.c \
+ ../src/format-javascript.c \
../src/format-csharp.c \
../src/format-awk.c \
../src/format-pascal.c \
@@ -1983,6 +1984,10 @@ format-scheme.lo: ../src/format-scheme.c
format-java.lo: ../src/format-java.c
$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o format-java.lo `test -f '../src/format-java.c' || echo '$(srcdir)/'`../src/format-java.c
+format-javascript.lo: ../src/format-javascript.c
+ $(AM_V_CC) @AM_BACKSLASH@
+ $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o format-javascript.lo `test -f '../src/format-javascript.c' || echo '$(srcdir)/'`../src/format-javascript.c
+
format-csharp.lo: ../src/format-csharp.c
$(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o format-csharp.lo `test -f '../src/format-csharp.c' || echo '$(srcdir)/'`../src/format-csharp.c
diff --git c/gettext-tools/src/Makefile.am i/gettext-tools/src/Makefile.am
index 87cc358..829bea3 100644
--- c/gettext-tools/src/Makefile.am
+++ i/gettext-tools/src/Makefile.am
@@ -51,7 +51,7 @@ write-qt.h \
po-time.h plural-table.h lang-table.h format.h filters.h \
xgettext.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-tcl.h x-perl.h x-php.h x-stringtable.h x-rst.h x-glade.h x-javascript.h
EXTRA_DIST += FILES project-id ChangeLog.0
@@ -135,7 +135,8 @@ FORMAT_SOURCE += \
format-qt.c \
format-qt-plural.c \
format-kde.c \
- format-boost.c
+ format-boost.c \
+ format-javascript.c
# libgettextsrc contains all code that is needed by at least two programs.
libgettextsrc_la_SOURCES = \
@@ -173,7 +174,7 @@ endif
xgettext_SOURCES += \
x-c.c x-po.c x-sh.c x-python.c x-lisp.c x-elisp.c x-librep.c x-scheme.c \
x-smalltalk.c x-java.c x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c \
- x-rst.c x-glade.c
+ x-rst.c x-glade.c x-javascript.c
if !WOE32DLL
msgattrib_SOURCES = msgattrib.c
else
diff --git c/gettext-tools/src/Makefile.in i/gettext-tools/src/Makefile.in
index ceeeb72..a37e6ba 100644
--- c/gettext-tools/src/Makefile.in
+++ i/gettext-tools/src/Makefile.in
@@ -375,7 +375,7 @@ am__objects_1 = message.lo po-error.lo po-xerror.lo \
@WOE32DLL_FALSE@ format-php.lo format-gcc-internal.lo \
@WOE32DLL_FALSE@ format-gfc-internal.lo format-qt.lo \
@WOE32DLL_FALSE@ format-qt-plural.lo format-kde.lo \
-@WOE32DLL_FALSE@ format-boost.lo
+@WOE32DLL_FALSE@ format-boost.lo format-javascript.lo
@WOE32DLL_TRUE@am__objects_2 = c++format.lo format-c.lo format-sh.lo \
@WOE32DLL_TRUE@ format-python.lo format-lisp.lo format-elisp.lo \
@WOE32DLL_TRUE@ format-librep.lo format-scheme.lo \
@@ -385,7 +385,7 @@ am__objects_1 = message.lo po-error.lo po-xerror.lo \
@WOE32DLL_TRUE@ format-php.lo format-gcc-internal.lo \
@WOE32DLL_TRUE@ format-gfc-internal.lo format-qt.lo \
@WOE32DLL_TRUE@ format-qt-plural.lo format-kde.lo \
-@WOE32DLL_TRUE@ format-boost.lo
+@WOE32DLL_TRUE@ format-boost.lo format-javascript.lo
@WOE32DLL_TRUE@am__objects_3 = gettextsrc-exports.lo
am_libgettextsrc_la_OBJECTS = $(am__objects_1) read-catalog.lo \
color.lo write-catalog.lo write-properties.lo \
@@ -508,7 +508,7 @@ urlget_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
am__xgettext_SOURCES_DIST = xgettext.c x-c.c x-po.c x-sh.c x-python.c \
x-lisp.c x-elisp.c x-librep.c x-scheme.c x-smalltalk.c \
x-java.c x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c \
- x-rst.c x-glade.c ../woe32dll/c++xgettext.cc
+ x-rst.c x-glade.c x-javascript.c ../woe32dll/c++xgettext.cc
@WOE32DLL_FALSE@am_xgettext_OBJECTS = xgettext-xgettext.$(OBJEXT) \
@WOE32DLL_FALSE@ xgettext-x-c.$(OBJEXT) xgettext-x-po.$(OBJEXT) \
@WOE32DLL_FALSE@ xgettext-x-sh.$(OBJEXT) \
@@ -526,7 +526,8 @@ am__xgettext_SOURCES_DIST = xgettext.c x-c.c x-po.c x-sh.c x-python.c \
@WOE32DLL_FALSE@ xgettext-x-perl.$(OBJEXT) \
@WOE32DLL_FALSE@ xgettext-x-php.$(OBJEXT) \
@WOE32DLL_FALSE@ xgettext-x-rst.$(OBJEXT) \
-@WOE32DLL_FALSE@ xgettext-x-glade.$(OBJEXT)
+@WOE32DLL_FALSE@ xgettext-x-glade.$(OBJEXT) \
+@WOE32DLL_FALSE@ xgettext-x-javascript.$(OBJEXT)
@WOE32DLL_TRUE@am_xgettext_OBJECTS = xgettext-c++xgettext.$(OBJEXT) \
@WOE32DLL_TRUE@ xgettext-x-c.$(OBJEXT) xgettext-x-po.$(OBJEXT) \
@WOE32DLL_TRUE@ xgettext-x-sh.$(OBJEXT) \
@@ -544,7 +545,8 @@ am__xgettext_SOURCES_DIST = xgettext.c x-c.c x-po.c x-sh.c x-python.c \
@WOE32DLL_TRUE@ xgettext-x-perl.$(OBJEXT) \
@WOE32DLL_TRUE@ xgettext-x-php.$(OBJEXT) \
@WOE32DLL_TRUE@ xgettext-x-rst.$(OBJEXT) \
-@WOE32DLL_TRUE@ xgettext-x-glade.$(OBJEXT)
+@WOE32DLL_TRUE@ xgettext-x-glade.$(OBJEXT) \
+@WOE32DLL_TRUE@ xgettext-x-javascript.$(OBJEXT)
xgettext_OBJECTS = $(am_xgettext_OBJECTS)
AM_V_P = $(am__v_P_@AM_V@)
am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
@@ -1746,7 +1748,7 @@ write-qt.h \
po-time.h plural-table.h lang-table.h format.h filters.h \
xgettext.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-tcl.h x-perl.h x-php.h x-stringtable.h x-rst.h x-glade.h x-javascript.h
aliaspath = $(localedir)
jardir = $(datadir)/gettext
@@ -1788,7 +1790,8 @@ dir-list.c str-list.c
@WOE32DLL_FALSE@ format-tcl.c format-perl.c format-perl-brace.c \
@WOE32DLL_FALSE@ format-php.c format-gcc-internal.c \
@WOE32DLL_FALSE@ format-gfc-internal.c format-qt.c \
-@WOE32DLL_FALSE@ format-qt-plural.c format-kde.c format-boost.c
+@WOE32DLL_FALSE@ format-qt-plural.c format-kde.c format-boost.c \
+@WOE32DLL_FALSE@ format-javascript.c
@WOE32DLL_TRUE@FORMAT_SOURCE = ../woe32dll/c++format.cc \
@WOE32DLL_TRUE@ format-invalid.h format-c.c format-c-parse.h \
@WOE32DLL_TRUE@ format-sh.c format-python.c format-lisp.c \
@@ -1798,7 +1801,7 @@ dir-list.c str-list.c
@WOE32DLL_TRUE@ format-perl.c format-perl-brace.c format-php.c \
@WOE32DLL_TRUE@ format-gcc-internal.c format-gfc-internal.c \
@WOE32DLL_TRUE@ format-qt.c format-qt-plural.c format-kde.c \
-@WOE32DLL_TRUE@ format-boost.c
+@WOE32DLL_TRUE@ format-boost.c format-javascript.c
# libgettextsrc contains all code that is needed by at least two programs.
libgettextsrc_la_SOURCES = $(COMMON_SOURCE) read-catalog.c color.c \
@@ -1826,12 +1829,12 @@ msgunfmt_SOURCES = msgunfmt.c read-mo.c read-java.c read-csharp.c \
@WOE32DLL_FALSE@ x-python.c x-lisp.c x-elisp.c x-librep.c \
@WOE32DLL_FALSE@ x-scheme.c x-smalltalk.c x-java.c x-csharp.c \
@WOE32DLL_FALSE@ x-awk.c x-ycp.c x-tcl.c x-perl.c x-php.c \
-@WOE32DLL_FALSE@ x-rst.c x-glade.c
+@WOE32DLL_FALSE@ x-rst.c x-glade.c x-javascript.c
@WOE32DLL_TRUE@xgettext_SOURCES = ../woe32dll/c++xgettext.cc x-c.c \
@WOE32DLL_TRUE@ x-po.c x-sh.c x-python.c x-lisp.c x-elisp.c \
@WOE32DLL_TRUE@ x-librep.c x-scheme.c x-smalltalk.c x-java.c \
@WOE32DLL_TRUE@ x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c \
-@WOE32DLL_TRUE@ x-php.c x-rst.c x-glade.c
+@WOE32DLL_TRUE@ x-php.c x-rst.c x-glade.c x-javascript.c
@WOE32DLL_FALSE@msgattrib_SOURCES = msgattrib.c
@WOE32DLL_TRUE@msgattrib_SOURCES = ../woe32dll/c++msgattrib.cc
@WOE32DLL_FALSE@msgcat_SOURCES = msgcat.c
@@ -2650,6 +2653,14 @@ xgettext-x-glade.o: x-glade.c
xgettext-x-glade.obj: x-glade.c
$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-glade.obj `if test -f 'x-glade.c'; then $(CYGPATH_W) 'x-glade.c'; else $(CYGPATH_W) '$(srcdir)/x-glade.c'; fi`
+xgettext-x-javascript.o: x-javascript.c
+ $(AM_V_CC) @AM_BACKSLASH@
+ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-javascript.o `test -f 'x-javascript.c' || echo '$(srcdir)/'`x-javascript.c
+
+xgettext-x-javascript.obj: x-javascript.c
+ $(AM_V_CC) @AM_BACKSLASH@
+ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(xgettext_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o xgettext-x-javascript.obj `if test -f 'x-javascript.c'; then $(CYGPATH_W) 'x-javascript.c'; else $(CYGPATH_W) '$(srcdir)/x-javascript.c'; fi`
+
.cc.o:
$(AM_V_CXX)$(CXXCOMPILE) -c -o $@ $<
diff --git c/gettext-tools/src/format-javascript.c i/gettext-tools/src/format-javascript.c
new file mode 100644
index 0000000..5705972
--- /dev/null
+++ i/gettext-tools/src/format-javascript.c
@@ -0,0 +1,669 @@
+/* JavaScript format strings.
+ Copyright (C) 2001-2004, 2006-2009 Free Software Foundation, Inc.
+ Written by Andreas Stricker <[email protected]>, 2010.
+ It's based on python format module from Bruno Haible.
+
+ 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 <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "format.h"
+#include "c-ctype.h"
+#include "xalloc.h"
+#include "xvasprintf.h"
+#include "format-invalid.h"
+#include "gettext.h"
+
+#define _(str) gettext (str)
+
+/* Gettext itself doesn't know any format string. But there are
+ plenty of libraries using any kind of format strings:
+ * JS-Gettext allows %1 %2 ...
+ * Prototype JS has templates #{name}
+ * ExtJS has String.format with {1} {2} ... and Templates with {name}
+ */
+
+enum format_arg_type
+{
+ FAT_NONE,
+ FAT_ANY,
+ FAT_CHARACTER,
+ FAT_STRING,
+ FAT_INTEGER,
+ FAT_FLOAT
+};
+
+struct named_arg
+{
+ char *name;
+ enum format_arg_type type;
+};
+
+struct unnamed_arg
+{
+ enum format_arg_type type;
+};
+
+struct spec
+{
+ unsigned int directives;
+ unsigned int named_arg_count;
+ unsigned int unnamed_arg_count;
+ unsigned int allocated;
+ struct named_arg *named;
+ struct unnamed_arg *unnamed;
+};
+
+/* Locale independent test for a decimal digit.
+ Argument can be 'char' or 'unsigned char'. (Whereas the argument of
+ <ctype.h> isdigit must be an 'unsigned char'.) */
+#undef isdigit
+#define isdigit(c) ((unsigned int) ((c) - '0') < 10)
+
+
+static int
+named_arg_compare (const void *p1, const void *p2)
+{
+ return strcmp (((const struct named_arg *) p1)->name,
+ ((const struct named_arg *) p2)->name);
+}
+
+#define INVALID_MIXES_NAMED_UNNAMED() \
+ xstrdup (_("The string refers to arguments both through argument names and through unnamed argument specifications."))
+
+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;
+
+ spec.directives = 0;
+ spec.named_arg_count = 0;
+ spec.unnamed_arg_count = 0;
+ spec.allocated = 0;
+ spec.named = NULL;
+ spec.unnamed = NULL;
+
+ for (; *format != '\0';)
+ if (*format++ == '%')
+ {
+ /* A directive. */
+ char *name = NULL;
+ bool zero_precision = false;
+ enum format_arg_type type;
+
+ FDI_SET (format - 1, FMTDIR_START);
+ spec.directives++;
+
+ if (*format == '(')
+ {
+ unsigned int depth;
+ const char *name_start;
+ const char *name_end;
+ size_t n;
+
+ name_start = ++format;
+ depth = 0;
+ for (; *format != '\0'; format++)
+ {
+ if (*format == '(')
+ depth++;
+ else if (*format == ')')
+ {
+ if (depth == 0)
+ break;
+ else
+ depth--;
+ }
+ }
+ if (*format == '\0')
+ {
+ *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
+ name_end = format++;
+
+ n = name_end - name_start;
+ name = XNMALLOC (n + 1, char);
+ memcpy (name, name_start, n);
+ name[n] = '\0';
+ }
+
+ while (*format == '-' || *format == '+' || *format == ' '
+ || *format == '#' || *format == '0')
+ format++;
+
+ if (*format == '*')
+ {
+ format++;
+
+ /* Named and unnamed specifications are exclusive. */
+ if (spec.named_arg_count > 0)
+ {
+ *invalid_reason = INVALID_MIXES_NAMED_UNNAMED ();
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (spec.allocated == spec.unnamed_arg_count)
+ {
+ spec.allocated = 2 * spec.allocated + 1;
+ spec.unnamed = (struct unnamed_arg *) xrealloc (spec.unnamed, spec.allocated * sizeof (struct unnamed_arg));
+ }
+ spec.unnamed[spec.unnamed_arg_count].type = FAT_INTEGER;
+ spec.unnamed_arg_count++;
+ }
+ else if (isdigit (*format))
+ {
+ do format++; while (isdigit (*format));
+ }
+
+ if (*format == '.')
+ {
+ format++;
+
+ if (*format == '*')
+ {
+ format++;
+
+ /* Named and unnamed specifications are exclusive. */
+ if (spec.named_arg_count > 0)
+ {
+ *invalid_reason = INVALID_MIXES_NAMED_UNNAMED ();
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (spec.allocated == spec.unnamed_arg_count)
+ {
+ spec.allocated = 2 * spec.allocated + 1;
+ spec.unnamed = (struct unnamed_arg *) xrealloc (spec.unnamed, spec.allocated * sizeof (struct unnamed_arg));
+ }
+ spec.unnamed[spec.unnamed_arg_count].type = FAT_INTEGER;
+ spec.unnamed_arg_count++;
+ }
+ else if (isdigit (*format))
+ {
+ zero_precision = true;
+ do
+ {
+ if (*format != '0')
+ zero_precision = false;
+ format++;
+ }
+ while (isdigit (*format));
+ }
+ }
+
+ if (*format == 'h' || *format == 'l' || *format == 'L')
+ format++;
+
+ switch (*format)
+ {
+ case '%':
+ type = FAT_NONE;
+ break;
+ case 'c':
+ type = FAT_CHARACTER;
+ break;
+ case 's': case 'r':
+ type = (zero_precision ? FAT_ANY : FAT_STRING);
+ break;
+ case 'i': case 'd': case 'u': case 'o': case 'x': case 'X':
+ type = FAT_INTEGER;
+ break;
+ case 'e': case 'E': case 'f': case 'g': case 'G':
+ type = FAT_FLOAT;
+ 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;
+ }
+
+ if (name != NULL)
+ {
+ /* Named argument. */
+
+ /* Named and unnamed specifications are exclusive. */
+ if (spec.unnamed_arg_count > 0)
+ {
+ *invalid_reason = INVALID_MIXES_NAMED_UNNAMED ();
+ FDI_SET (format, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (spec.allocated == spec.named_arg_count)
+ {
+ spec.allocated = 2 * spec.allocated + 1;
+ spec.named = (struct named_arg *) xrealloc (spec.named, spec.allocated * sizeof (struct named_arg));
+ }
+ spec.named[spec.named_arg_count].name = name;
+ spec.named[spec.named_arg_count].type = type;
+ spec.named_arg_count++;
+ }
+ else if (*format != '%')
+ {
+ /* Unnamed argument. */
+
+ /* Named and unnamed specifications are exclusive. */
+ if (spec.named_arg_count > 0)
+ {
+ *invalid_reason = INVALID_MIXES_NAMED_UNNAMED ();
+ FDI_SET (format, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (spec.allocated == spec.unnamed_arg_count)
+ {
+ spec.allocated = 2 * spec.allocated + 1;
+ spec.unnamed = (struct unnamed_arg *) xrealloc (spec.unnamed, spec.allocated * sizeof (struct unnamed_arg));
+ }
+ spec.unnamed[spec.unnamed_arg_count].type = type;
+ spec.unnamed_arg_count++;
+ }
+
+ FDI_SET (format, FMTDIR_END);
+
+ format++;
+ }
+
+ /* Sort the named argument array, and eliminate duplicates. */
+ if (spec.named_arg_count > 1)
+ {
+ unsigned int i, j;
+ bool err;
+
+ qsort (spec.named, spec.named_arg_count, sizeof (struct named_arg),
+ named_arg_compare);
+
+ /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */
+ err = false;
+ for (i = j = 0; i < spec.named_arg_count; i++)
+ if (j > 0 && strcmp (spec.named[i].name, spec.named[j-1].name) == 0)
+ {
+ enum format_arg_type type1 = spec.named[i].type;
+ enum format_arg_type type2 = spec.named[j-1].type;
+ enum format_arg_type type_both;
+
+ if (type1 == type2 || type2 == FAT_ANY)
+ type_both = type1;
+ else if (type1 == FAT_ANY)
+ type_both = type2;
+ else
+ {
+ /* Incompatible types. */
+ type_both = FAT_NONE;
+ if (!err)
+ *invalid_reason =
+ xasprintf (_("The string refers to the argument named '%s' in incompatible ways."), spec.named[i].name);
+ err = true;
+ }
+
+ spec.named[j-1].type = type_both;
+ free (spec.named[i].name);
+ }
+ else
+ {
+ if (j < i)
+ {
+ spec.named[j].name = spec.named[i].name;
+ spec.named[j].type = spec.named[i].type;
+ }
+ j++;
+ }
+ spec.named_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.named != NULL)
+ {
+ unsigned int i;
+ for (i = 0; i < spec.named_arg_count; i++)
+ free (spec.named[i].name);
+ free (spec.named);
+ }
+ if (spec.unnamed != NULL)
+ free (spec.unnamed);
+ return NULL;
+}
+
+static void
+format_free (void *descr)
+{
+ struct spec *spec = (struct spec *) descr;
+
+ if (spec->named != NULL)
+ {
+ unsigned int i;
+ for (i = 0; i < spec->named_arg_count; i++)
+ free (spec->named[i].name);
+ free (spec->named);
+ }
+ if (spec->unnamed != NULL)
+ free (spec->unnamed);
+ 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->named_arg_count > 0 && spec2->unnamed_arg_count > 0)
+ {
+ if (error_logger)
+ error_logger (_("format specifications in '%s' expect a mapping, those in '%s' expect a tuple"),
+ pretty_msgid, pretty_msgstr);
+ err = true;
+ }
+ else if (spec1->unnamed_arg_count > 0 && spec2->named_arg_count > 0)
+ {
+ if (error_logger)
+ error_logger (_("format specifications in '%s' expect a tuple, those in '%s' expect a mapping"),
+ pretty_msgid, pretty_msgstr);
+ err = true;
+ }
+ else
+ {
+ if (spec1->named_arg_count + spec2->named_arg_count > 0)
+ {
+ unsigned int i, j;
+ unsigned int n1 = spec1->named_arg_count;
+ unsigned int n2 = spec2->named_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 :
+ strcmp (spec1->named[i].name, spec2->named[j].name));
+
+ if (cmp > 0)
+ {
+ if (error_logger)
+ error_logger (_("a format specification for argument '%s', as in '%s', doesn't exist in '%s'"),
+ spec2->named[j].name, pretty_msgstr,
+ pretty_msgid);
+ err = true;
+ break;
+ }
+ else if (cmp < 0)
+ {
+ if (equality)
+ {
+ if (error_logger)
+ error_logger (_("a format specification for argument '%s' doesn't exist in '%s'"),
+ spec1->named[i].name, 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 (strcmp (spec1->named[i].name, spec2->named[j].name) == 0)
+ {
+ if (!(spec1->named[i].type == spec2->named[j].type
+ || (!equality
+ && (spec1->named[i].type == FAT_ANY
+ || spec2->named[j].type == FAT_ANY))))
+ {
+ if (error_logger)
+ error_logger (_("format specifications in '%s' and '%s' for argument '%s' are not the same"),
+ pretty_msgid, pretty_msgstr,
+ spec2->named[j].name);
+ err = true;
+ break;
+ }
+ j++, i++;
+ }
+ else
+ i++;
+ }
+ }
+
+ if (spec1->unnamed_arg_count + spec2->unnamed_arg_count > 0)
+ {
+ unsigned int i;
+
+ /* Check the argument types are the same. */
+ if (spec1->unnamed_arg_count != spec2->unnamed_arg_count)
+ {
+ if (error_logger)
+ error_logger (_("number of format specifications in '%s' and '%s' does not match"),
+ pretty_msgid, pretty_msgstr);
+ err = true;
+ }
+ else
+ for (i = 0; i < spec2->unnamed_arg_count; i++)
+ if (!(spec1->unnamed[i].type == spec2->unnamed[i].type
+ || (!equality
+ && (spec1->unnamed[i].type == FAT_ANY
+ || spec2->unnamed[i].type == FAT_ANY))))
+ {
+ if (error_logger)
+ error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"),
+ pretty_msgid, pretty_msgstr, i + 1);
+ err = true;
+ }
+ }
+ }
+
+ return err;
+}
+
+
+struct formatstring_parser formatstring_javascript =
+{
+ format_parse,
+ format_free,
+ format_get_number_of_directives,
+ NULL,
+ format_check
+};
+
+
+unsigned int
+get_javascript_format_unnamed_arg_count (const char *string)
+{
+ /* Parse the format string. */
+ char *invalid_reason = NULL;
+ struct spec *descr =
+ (struct spec *) format_parse (string, false, NULL, &invalid_reason);
+
+ if (descr != NULL)
+ {
+ unsigned int result = descr->unnamed_arg_count;
+
+ format_free (descr);
+ return result;
+ }
+ else
+ {
+ free (invalid_reason);
+ return 0;
+ }
+}
+
+
+#ifdef TEST
+
+/* Test program: Print the argument list specification returned by
+ format_parse for strings read from standard input. */
+
+#include <stdio.h>
+
+static void
+format_print (void *descr)
+{
+ struct spec *spec = (struct spec *) descr;
+ unsigned int i;
+
+ if (spec == NULL)
+ {
+ printf ("INVALID");
+ return;
+ }
+
+ if (spec->named_arg_count > 0)
+ {
+ if (spec->unnamed_arg_count > 0)
+ abort ();
+
+ printf ("{");
+ for (i = 0; i < spec->named_arg_count; i++)
+ {
+ if (i > 0)
+ printf (", ");
+ printf ("'%s':", spec->named[i].name);
+ switch (spec->named[i].type)
+ {
+ case FAT_ANY:
+ printf ("*");
+ break;
+ case FAT_CHARACTER:
+ printf ("c");
+ break;
+ case FAT_STRING:
+ printf ("s");
+ break;
+ case FAT_INTEGER:
+ printf ("i");
+ break;
+ case FAT_FLOAT:
+ printf ("f");
+ break;
+ default:
+ abort ();
+ }
+ }
+ printf ("}");
+ }
+ else
+ {
+ printf ("(");
+ for (i = 0; i < spec->unnamed_arg_count; i++)
+ {
+ if (i > 0)
+ printf (" ");
+ switch (spec->unnamed[i].type)
+ {
+ case FAT_ANY:
+ printf ("*");
+ break;
+ case FAT_CHARACTER:
+ printf ("c");
+ break;
+ case FAT_STRING:
+ printf ("s");
+ break;
+ case FAT_INTEGER:
+ printf ("i");
+ break;
+ case FAT_FLOAT:
+ printf ("f");
+ 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../intl -DHAVE_CONFIG_H -DTEST format-javascript.c ../gnulib-lib/libgettextlib.la"
+ * End:
+ */
+
+#endif /* TEST */
diff --git c/gettext-tools/src/format.c i/gettext-tools/src/format.c
index e6c5de9..22fc8c2 100644
--- c/gettext-tools/src/format.c
+++ i/gettext-tools/src/format.c
@@ -44,6 +44,7 @@ struct formatstring_parser *formatstring_parsers[NFORMATS] =
/* format_scheme */ &formatstring_scheme,
/* format_smalltalk */ &formatstring_smalltalk,
/* format_java */ &formatstring_java,
+ /* format_javascript */ &formatstring_javascript,
/* format_csharp */ &formatstring_csharp,
/* format_awk */ &formatstring_awk,
/* format_pascal */ &formatstring_pascal,
diff --git c/gettext-tools/src/format.h i/gettext-tools/src/format.h
index 60f0adc..679cd9a 100644
--- c/gettext-tools/src/format.h
+++ i/gettext-tools/src/format.h
@@ -105,6 +105,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_javascript;
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 c/gettext-tools/src/message.c i/gettext-tools/src/message.c
index 5162b06..e57ddab 100644
--- c/gettext-tools/src/message.c
+++ i/gettext-tools/src/message.c
@@ -44,6 +44,7 @@ const char *const format_language[NFORMATS] =
/* format_scheme */ "scheme",
/* format_smalltalk */ "smalltalk",
/* format_java */ "java",
+ /* format_javascript */ "javascript",
/* format_csharp */ "csharp",
/* format_awk */ "awk",
/* format_pascal */ "object-pascal",
@@ -72,6 +73,7 @@ const char *const format_language_pretty[NFORMATS] =
/* format_scheme */ "Scheme",
/* format_smalltalk */ "Smalltalk",
/* format_java */ "Java",
+ /* format_javascript */ "JavaScript",
/* format_csharp */ "C#",
/* format_awk */ "awk",
/* format_pascal */ "Object Pascal",
diff --git c/gettext-tools/src/message.h i/gettext-tools/src/message.h
index af9244a..0f200b1 100644
--- c/gettext-tools/src/message.h
+++ i/gettext-tools/src/message.h
@@ -53,6 +53,7 @@ enum format_type
format_scheme,
format_smalltalk,
format_java,
+ format_javascript,
format_csharp,
format_awk,
format_pascal,
@@ -68,7 +69,7 @@ enum format_type
format_kde,
format_boost
};
-#define NFORMATS 24 /* Number of format_type enum values. */
+#define NFORMATS 25 /* 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 c/gettext-tools/src/x-javascript.c i/gettext-tools/src/x-javascript.c
new file mode 100644
index 0000000..f97308c
--- /dev/null
+++ i/gettext-tools/src/x-javascript.c
@@ -0,0 +1,1809 @@
+/* xgettext JavaScript backend.
+ Copyright (C) 2002-2003, 2005-2009 Free Software Foundation, Inc.
+
+ This file was written by Andreas Stricker <[email protected]>, 2010
+ It's based on x-python from Bruno Haible.
+
+ 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 <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+/* Specification. */
+#include "x-javascript.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "message.h"
+#include "xgettext.h"
+#include "error.h"
+#include "error-progname.h"
+#include "progname.h"
+#include "basename.h"
+#include "xerror.h"
+#include "xvasprintf.h"
+#include "xalloc.h"
+#include "c-strstr.h"
+#include "c-ctype.h"
+#include "po-charset.h"
+#include "uniname.h"
+#include "unistr.h"
+#include "gettext.h"
+
+#define _(s) gettext(s)
+
+#define max(a,b) ((a) > (b) ? (a) : (b))
+
+#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
+
+/* The JavaScript aka ECMA-Script syntax is defined in ECMA-262
+ specification:
+ http://www.ecma-international.org/publications/standards/Ecma-262.htm */
+
+/* ====================== Keyword set customization. ====================== */
+
+/* If true extract all strings. */
+static bool extract_all = false;
+
+static hash_table keywords;
+static bool default_keywords = true;
+
+
+void
+x_javascript_extract_all ()
+{
+ extract_all = true;
+}
+
+
+void
+x_javascript_keyword (const char *name)
+{
+ if (name == NULL)
+ default_keywords = false;
+ else
+ {
+ const char *end;
+ struct callshape shape;
+ const char *colon;
+
+ if (keywords.table == NULL)
+ hash_init (&keywords, 100);
+
+ split_keywordspec (name, &end, &shape);
+
+ /* The characters between name and end should form a valid C identifier.
+ A colon means an invalid parse in split_keywordspec(). */
+ colon = strchr (name, ':');
+ if (colon == NULL || colon >= end)
+ insert_keyword_callshape (&keywords, name, end - name, &shape);
+ }
+}
+
+/* Finish initializing the keywords hash table.
+ Called after argument processing, before each file is processed. */
+static void
+init_keywords ()
+{
+ if (default_keywords)
+ {
+ /* When adding new keywords here, also update the documentation in
+ xgettext.texi! */
+ x_javascript_keyword ("gettext");
+ x_javascript_keyword ("ugettext");
+ x_javascript_keyword ("dgettext:2");
+ x_javascript_keyword ("ngettext:1,2");
+ x_javascript_keyword ("ungettext:1,2");
+ x_javascript_keyword ("dngettext:2,3");
+ x_javascript_keyword ("_");
+ default_keywords = false;
+ }
+}
+
+void
+init_flag_table_javascript ()
+{
+ xgettext_record_flag ("gettext:1:pass-javascript-format");
+ xgettext_record_flag ("ugettext:1:pass-javascript-format");
+ xgettext_record_flag ("dgettext:2:pass-javascript-format");
+ xgettext_record_flag ("ngettext:1:pass-javascript-format");
+ xgettext_record_flag ("ngettext:2:pass-javascript-format");
+ xgettext_record_flag ("ungettext:1:pass-javascript-format");
+ xgettext_record_flag ("ungettext:2:pass-javascript-format");
+ xgettext_record_flag ("dngettext:2:pass-javascript-format");
+ xgettext_record_flag ("dngettext:3:pass-javascript-format");
+ xgettext_record_flag ("_:1:pass-javascript-format");
+}
+
+
+/* ======================== Reading of characters. ======================== */
+
+/* Real filename, used in error messages about the input file. */
+static const char *real_file_name;
+
+/* Logical filename and line number, used to label the extracted messages. */
+static char *logical_file_name;
+static int line_number;
+
+/* The input file stream. */
+static FILE *fp;
+
+
+/* 1. line_number handling. */
+
+/* Maximum used, roughly a safer MB_LEN_MAX. */
+#define MAX_PHASE1_PUSHBACK 16
+static unsigned char phase1_pushback[MAX_PHASE1_PUSHBACK];
+static int phase1_pushback_length;
+
+/* Read the next single byte from the input file. */
+static int
+phase1_getc ()
+{
+ int c;
+
+ if (phase1_pushback_length)
+ c = phase1_pushback[--phase1_pushback_length];
+ else
+ {
+ c = getc (fp);
+
+ if (c == EOF)
+ {
+ if (ferror (fp))
+ error (EXIT_FAILURE, errno, _("error while reading \"%s\""),
+ real_file_name);
+ return EOF;
+ }
+ }
+
+ if (c == '\n')
+ ++line_number;
+
+ return c;
+}
+
+/* Supports MAX_PHASE1_PUSHBACK characters of pushback. */
+static void
+phase1_ungetc (int c)
+{
+ if (c != EOF)
+ {
+ if (c == '\n')
+ --line_number;
+
+ if (phase1_pushback_length == SIZEOF (phase1_pushback))
+ abort ();
+ phase1_pushback[phase1_pushback_length++] = c;
+ }
+}
+
+
+/* Phase 2: Conversion to Unicode.
+ For now, we expect Javascript files to be encoded as UTF-8 */
+
+/* End-of-file indicator for functions returning an UCS-4 character. */
+#define UEOF -1
+
+static lexical_context_ty lexical_context;
+
+static int phase2_pushback[max (9, UNINAME_MAX + 3)];
+static int phase2_pushback_length;
+
+/* Read the next Unicode UCS-4 character from the input file. */
+static int
+phase2_getc ()
+{
+ if (phase2_pushback_length)
+ return phase2_pushback[--phase2_pushback_length];
+
+ if (xgettext_current_source_encoding == po_charset_ascii)
+ {
+ int c = phase1_getc ();
+ if (c == EOF)
+ return UEOF;
+ if (!c_isascii (c))
+ {
+ multiline_error (xstrdup (""),
+ xasprintf ("%s\n%s\n",
+ non_ascii_error_message (lexical_context,
+ real_file_name,
+ line_number),
+ _("\
+Please specify the source encoding through --from-code\n")));
+ exit (EXIT_FAILURE);
+ }
+ return c;
+ }
+ else if (xgettext_current_source_encoding != po_charset_utf8)
+ {
+#if HAVE_ICONV
+ /* Use iconv on an increasing number of bytes. Read only as many bytes
+ through phase1_getc as needed. This is needed to give reasonable
+ interactive behaviour when fp is connected to an interactive tty. */
+ unsigned char buf[MAX_PHASE1_PUSHBACK];
+ size_t bufcount;
+ int c = phase1_getc ();
+ if (c == EOF)
+ return UEOF;
+ buf[0] = (unsigned char) c;
+ bufcount = 1;
+
+ for (;;)
+ {
+ unsigned char scratchbuf[6];
+ const char *inptr = (const char *) &buf[0];
+ size_t insize = bufcount;
+ char *outptr = (char *) &scratchbuf[0];
+ size_t outsize = sizeof (scratchbuf);
+
+ size_t res = iconv (xgettext_current_source_iconv,
+ (ICONV_CONST char **) &inptr, &insize,
+ &outptr, &outsize);
+ /* We expect that a character has been produced if and only if
+ some input bytes have been consumed. */
+ if ((insize < bufcount) != (outsize < sizeof (scratchbuf)))
+ abort ();
+ if (outsize == sizeof (scratchbuf))
+ {
+ /* No character has been produced. Must be an error. */
+ if (res != (size_t)(-1))
+ abort ();
+
+ if (errno == EILSEQ)
+ {
+ /* An invalid multibyte sequence was encountered. */
+ multiline_error (xstrdup (""),
+ xasprintf (_("\
+%s:%d: Invalid multibyte sequence.\n\
+Please specify the correct source encoding through --from-code\n"),
+ real_file_name, line_number));
+ exit (EXIT_FAILURE);
+ }
+ else if (errno == EINVAL)
+ {
+ /* An incomplete multibyte character. */
+ int c;
+
+ if (bufcount == MAX_PHASE1_PUSHBACK)
+ {
+ /* An overlong incomplete multibyte sequence was
+ encountered. */
+ multiline_error (xstrdup (""),
+ xasprintf (_("\
+%s:%d: Long incomplete multibyte sequence.\n\
+Please specify the correct source encoding through --from-code\n"),
+ real_file_name, line_number));
+ exit (EXIT_FAILURE);
+ }
+
+ /* Read one more byte and retry iconv. */
+ c = phase1_getc ();
+ if (c == EOF)
+ {
+ multiline_error (xstrdup (""),
+ xasprintf (_("\
+%s:%d: Incomplete multibyte sequence at end of file.\n\
+Please specify the correct source encoding through --from-code\n"),
+ real_file_name, line_number));
+ exit (EXIT_FAILURE);
+ }
+ if (c == '\n')
+ {
+ multiline_error (xstrdup (""),
+ xasprintf (_("\
+%s:%d: Incomplete multibyte sequence at end of line.\n\
+Please specify the correct source encoding through --from-code\n"),
+ real_file_name, line_number - 1));
+ exit (EXIT_FAILURE);
+ }
+ buf[bufcount++] = (unsigned char) c;
+ }
+ else
+ error (EXIT_FAILURE, errno, _("%s:%d: iconv failure"),
+ real_file_name, line_number);
+ }
+ else
+ {
+ size_t outbytes = sizeof (scratchbuf) - outsize;
+ size_t bytes = bufcount - insize;
+ ucs4_t uc;
+
+ /* We expect that one character has been produced. */
+ if (bytes == 0)
+ abort ();
+ if (outbytes == 0)
+ abort ();
+ /* Push back the unused bytes. */
+ while (insize > 0)
+ phase1_ungetc (buf[--insize]);
+ /* Convert the character from UTF-8 to UCS-4. */
+ if (u8_mbtouc (&uc, scratchbuf, outbytes) < outbytes)
+ {
+ /* scratchbuf contains an out-of-range Unicode character
+ (> 0x10ffff). */
+ multiline_error (xstrdup (""),
+ xasprintf (_("\
+%s:%d: Invalid multibyte sequence.\n\
+Please specify the source encoding through --from-code\n"),
+ real_file_name, line_number));
+ exit (EXIT_FAILURE);
+ }
+ return uc;
+ }
+ }
+#else
+ /* If we don't have iconv(), the only supported values for
+ xgettext_global_source_encoding and thus also for
+ xgettext_current_source_encoding are ASCII and UTF-8. */
+ abort ();
+#endif
+ }
+ else
+ {
+ /* Read an UTF-8 encoded character. */
+ unsigned char buf[6];
+ unsigned int count;
+ int c;
+ ucs4_t uc;
+
+ c = phase1_getc ();
+ if (c == EOF)
+ return UEOF;
+ buf[0] = c;
+ count = 1;
+
+ if (buf[0] >= 0xc0)
+ {
+ c = phase1_getc ();
+ if (c == EOF)
+ return UEOF;
+ buf[1] = c;
+ count = 2;
+ }
+
+ if (buf[0] >= 0xe0
+ && ((buf[1] ^ 0x80) < 0x40))
+ {
+ c = phase1_getc ();
+ if (c == EOF)
+ return UEOF;
+ buf[2] = c;
+ count = 3;
+ }
+
+ if (buf[0] >= 0xf0
+ && ((buf[1] ^ 0x80) < 0x40)
+ && ((buf[2] ^ 0x80) < 0x40))
+ {
+ c = phase1_getc ();
+ if (c == EOF)
+ return UEOF;
+ buf[3] = c;
+ count = 4;
+ }
+
+ if (buf[0] >= 0xf8
+ && ((buf[1] ^ 0x80) < 0x40)
+ && ((buf[2] ^ 0x80) < 0x40)
+ && ((buf[3] ^ 0x80) < 0x40))
+ {
+ c = phase1_getc ();
+ if (c == EOF)
+ return UEOF;
+ buf[4] = c;
+ count = 5;
+ }
+
+ if (buf[0] >= 0xfc
+ && ((buf[1] ^ 0x80) < 0x40)
+ && ((buf[2] ^ 0x80) < 0x40)
+ && ((buf[3] ^ 0x80) < 0x40)
+ && ((buf[4] ^ 0x80) < 0x40))
+ {
+ c = phase1_getc ();
+ if (c == EOF)
+ return UEOF;
+ buf[5] = c;
+ count = 6;
+ }
+
+ u8_mbtouc (&uc, buf, count);
+ return uc;
+ }
+}
+
+/* Supports max (9, UNINAME_MAX + 3) pushback characters. */
+static void
+phase2_ungetc (int c)
+{
+ if (c != UEOF)
+ {
+ if (phase2_pushback_length == SIZEOF (phase2_pushback))
+ abort ();
+ phase2_pushback[phase2_pushback_length++] = c;
+ }
+}
+
+
+/* ========================= Accumulating strings. ======================== */
+
+/* A string buffer type that allows appending Unicode characters.
+ Returns the entire string in UTF-8 encoding. */
+
+struct unicode_string_buffer
+{
+ /* The part of the string that has already been converted to UTF-8. */
+ char *utf8_buffer;
+ size_t utf8_buflen;
+ size_t utf8_allocated;
+};
+
+/* Initialize a 'struct unicode_string_buffer' to empty. */
+static inline void
+init_unicode_string_buffer (struct unicode_string_buffer *bp)
+{
+ bp->utf8_buffer = NULL;
+ bp->utf8_buflen = 0;
+ bp->utf8_allocated = 0;
+}
+
+/* Auxiliary function: Ensure count more bytes are available in bp->utf8. */
+static inline void
+unicode_string_buffer_append_unicode_grow (struct unicode_string_buffer *bp,
+ size_t count)
+{
+ if (bp->utf8_buflen + count > bp->utf8_allocated)
+ {
+ size_t new_allocated = 2 * bp->utf8_allocated + 10;
+ if (new_allocated < bp->utf8_buflen + count)
+ new_allocated = bp->utf8_buflen + count;
+ bp->utf8_allocated = new_allocated;
+ bp->utf8_buffer = xrealloc (bp->utf8_buffer, new_allocated);
+ }
+}
+
+/* Auxiliary function: Append a Unicode character to bp->utf8.
+ uc must be < 0x110000. */
+static inline void
+unicode_string_buffer_append_unicode (struct unicode_string_buffer *bp,
+ unsigned int uc)
+{
+ unsigned char utf8buf[6];
+ int count = u8_uctomb (utf8buf, uc, 6);
+
+ if (count < 0)
+ /* The caller should have ensured that uc is not out-of-range. */
+ abort ();
+
+ unicode_string_buffer_append_unicode_grow (bp, count);
+ memcpy (bp->utf8_buffer + bp->utf8_buflen, utf8buf, count);
+ bp->utf8_buflen += count;
+}
+
+/* Return the string buffer's contents. */
+static char *
+unicode_string_buffer_result (struct unicode_string_buffer *bp)
+{
+ /* NUL-terminate it. */
+ unicode_string_buffer_append_unicode_grow (bp, 1);
+ bp->utf8_buffer[bp->utf8_buflen] = '\0';
+ /* Return it. */
+ return bp->utf8_buffer;
+}
+
+/* Free the memory pointed to by a 'struct unicode_string_buffer'. */
+static inline void
+free_unicode_string_buffer (struct unicode_string_buffer *bp)
+{
+ free (bp->utf8_buffer);
+}
+
+
+/* ======================== Accumulating comments. ======================== */
+
+
+/* Accumulating a single comment line. */
+
+static struct unicode_string_buffer comment_buffer;
+
+static inline void
+comment_start ()
+{
+ lexical_context = lc_comment;
+ comment_buffer.utf8_buflen = 0;
+}
+
+static inline bool
+comment_at_start ()
+{
+ return (comment_buffer.utf8_buflen == 0);
+}
+
+static inline void
+comment_add (int c)
+{
+ unicode_string_buffer_append_unicode (&comment_buffer, c);
+}
+
+static inline const char *
+comment_line_end ()
+{
+ char *buffer = unicode_string_buffer_result (&comment_buffer);
+ size_t buflen = strlen (buffer);
+
+ while (buflen >= 1
+ && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t'))
+ --buflen;
+ buffer[buflen] = '\0';
+ savable_comment_add (buffer);
+ lexical_context = lc_outside;
+ return buffer;
+}
+
+
+/* These are for tracking whether comments count as immediately before
+ keyword. */
+static int last_comment_line;
+static int last_non_comment_line;
+
+
+/* ======================== Recognizing comments. ======================== */
+
+
+/* Recognizing the "coding" comment.
+ JavaScript provides to different comments: C-Style or C++ style
+*/
+
+/* Canonicalized encoding name for the current input file. */
+static const char *xgettext_current_file_source_encoding;
+
+#if HAVE_ICONV
+/* Converter from xgettext_current_file_source_encoding to UTF-8 (except from
+ ASCII or UTF-8, when this conversion is a no-op). */
+static iconv_t xgettext_current_file_source_iconv;
+#endif
+
+/* Tracking whether the current line is a continuation line or contains a
+ non-blank character. */
+static bool continuation_or_nonblank_line = false;
+
+/* Phase 3: Outside strings, replace backslash-newline with nothing and a
+ comment with nothing. */
+
+static int
+phase3_getc ()
+{
+ int c;
+
+ for (;;)
+ {
+ c = phase2_getc ();
+ if (c == '\\')
+ {
+ c = phase2_getc ();
+ if (c != '\n')
+ {
+ phase2_ungetc (c);
+ /* This shouldn't happen usually, because "A backslash is
+ illegal elsewhere on a line outside a string literal." */
+ return '\\';
+ }
+ /* Eat backslash-newline. */
+ continuation_or_nonblank_line = true;
+ }
+ else if (c == '/')
+ {
+ c = phase2_getc ();
+ if (c == '/')
+ {
+ /* Eat a comment to the end of line. */
+ last_comment_line = line_number;
+ comment_start ();
+ for (;;)
+ {
+ c = phase2_getc ();
+ if (c == UEOF || c == '\n')
+ break;
+ /* We skip all leading white space, but not EOLs. */
+ if (!(comment_at_start () && (c == ' ' || c == '\t')))
+ comment_add (c);
+ }
+ continuation_or_nonblank_line = false;
+ return c;
+ }
+ else if (c == '*')
+ {
+ /* Eat a comment to the end marker. (like this one!) */
+ last_comment_line = line_number;
+ comment_start ();
+ for (;;)
+ {
+ c = phase2_getc ();
+ if (c == UEOF)
+ break;
+ if (c == '*')
+ {
+ c = phase2_getc ();
+ if (c == '/')
+ break;
+ else
+ phase2_ungetc (c);
+ }
+ comment_add (c);
+ }
+ continuation_or_nonblank_line = false;
+ }
+ else
+ {
+ phase2_ungetc (c);
+ return '/';
+ }
+ }
+ else
+ {
+ if (c == '\n')
+ continuation_or_nonblank_line = false;
+ else if (!(c == ' ' || c == '\t' || c == '\f'))
+ continuation_or_nonblank_line = true;
+ return c;
+ }
+ }
+}
+
+/* Supports only one pushback character. */
+static void
+phase3_ungetc (int c)
+{
+ phase2_ungetc (c);
+}
+
+
+/* ========================= Accumulating strings. ======================== */
+
+/* Return value of phase7_getuc when EOF is reached. */
+#define P7_EOF (-1)
+#define P7_STRING_END (-2)
+
+/* Convert an UTF-16 or UTF-32 code point to a return value that can be
+ distinguished from a single-byte return value. */
+#define UNICODE(code) (0x100 + (code))
+
+/* Test a return value of phase7_getuc whether it designates an UTF-16 or
+ UTF-32 code point. */
+#define IS_UNICODE(p7_result) ((p7_result) >= 0x100)
+
+/* Extract the UTF-16 or UTF-32 code of a return value that satisfies
+ IS_UNICODE. */
+#define UNICODE_VALUE(p7_result) ((p7_result) - 0x100)
+
+/* A string buffer type that allows appending bytes (in the
+ xgettext_current_source_encoding) or Unicode characters.
+ Returns the entire string in UTF-8 encoding. */
+
+struct mixed_string_buffer
+{
+ /* The part of the string that has already been converted to UTF-8. */
+ char *utf8_buffer;
+ size_t utf8_buflen;
+ size_t utf8_allocated;
+ /* The first half of an UTF-16 surrogate character. */
+ unsigned short utf16_surr;
+ /* The part of the string that is still in the source encoding. */
+ char *curr_buffer;
+ size_t curr_buflen;
+ size_t curr_allocated;
+ /* The lexical context. Used only for error message purposes. */
+ lexical_context_ty lcontext;
+};
+
+/* Initialize a 'struct mixed_string_buffer' to empty. */
+static inline void
+init_mixed_string_buffer (struct mixed_string_buffer *bp, lexical_context_ty lcontext)
+{
+ bp->utf8_buffer = NULL;
+ bp->utf8_buflen = 0;
+ bp->utf8_allocated = 0;
+ bp->utf16_surr = 0;
+ bp->curr_buffer = NULL;
+ bp->curr_buflen = 0;
+ bp->curr_allocated = 0;
+ bp->lcontext = lcontext;
+}
+
+/* Auxiliary function: Append a byte to bp->curr. */
+static inline void
+mixed_string_buffer_append_byte (struct mixed_string_buffer *bp, unsigned char c)
+{
+ if (bp->curr_buflen == bp->curr_allocated)
+ {
+ bp->curr_allocated = 2 * bp->curr_allocated + 10;
+ bp->curr_buffer = xrealloc (bp->curr_buffer, bp->curr_allocated);
+ }
+ bp->curr_buffer[bp->curr_buflen++] = c;
+}
+
+/* Auxiliary function: Ensure count more bytes are available in bp->utf8. */
+static inline void
+mixed_string_buffer_append_unicode_grow (struct mixed_string_buffer *bp, size_t count)
+{
+ if (bp->utf8_buflen + count > bp->utf8_allocated)
+ {
+ size_t new_allocated = 2 * bp->utf8_allocated + 10;
+ if (new_allocated < bp->utf8_buflen + count)
+ new_allocated = bp->utf8_buflen + count;
+ bp->utf8_allocated = new_allocated;
+ bp->utf8_buffer = xrealloc (bp->utf8_buffer, new_allocated);
+ }
+}
+
+/* Auxiliary function: Append a Unicode character to bp->utf8.
+ uc must be < 0x110000. */
+static inline void
+mixed_string_buffer_append_unicode (struct mixed_string_buffer *bp, ucs4_t uc)
+{
+ unsigned char utf8buf[6];
+ int count = u8_uctomb (utf8buf, uc, 6);
+
+ if (count < 0)
+ /* The caller should have ensured that uc is not out-of-range. */
+ abort ();
+
+ mixed_string_buffer_append_unicode_grow (bp, count);
+ memcpy (bp->utf8_buffer + bp->utf8_buflen, utf8buf, count);
+ bp->utf8_buflen += count;
+}
+
+/* Auxiliary function: Flush bp->utf16_surr into bp->utf8_buffer. */
+static inline void
+mixed_string_buffer_flush_utf16_surr (struct mixed_string_buffer *bp)
+{
+ if (bp->utf16_surr != 0)
+ {
+ /* A half surrogate is invalid, therefore use U+FFFD instead. */
+ mixed_string_buffer_append_unicode (bp, 0xfffd);
+ bp->utf16_surr = 0;
+ }
+}
+
+/* Auxiliary function: Flush bp->curr_buffer into bp->utf8_buffer. */
+static inline void
+mixed_string_buffer_flush_curr_buffer (struct mixed_string_buffer *bp, int lineno)
+{
+ if (bp->curr_buflen > 0)
+ {
+ char *curr;
+ size_t count;
+
+ mixed_string_buffer_append_byte (bp, '\0');
+
+ /* Convert from the source encoding to UTF-8. */
+ curr = from_current_source_encoding (bp->curr_buffer, bp->lcontext,
+ logical_file_name, lineno);
+
+ /* Append it to bp->utf8_buffer. */
+ count = strlen (curr);
+ mixed_string_buffer_append_unicode_grow (bp, count);
+ memcpy (bp->utf8_buffer + bp->utf8_buflen, curr, count);
+ bp->utf8_buflen += count;
+
+ if (curr != bp->curr_buffer)
+ free (curr);
+ bp->curr_buflen = 0;
+ }
+}
+
+/* Append a character or Unicode character to a 'struct mixed_string_buffer'. */
+static void
+mixed_string_buffer_append (struct mixed_string_buffer *bp, int c)
+{
+ if (IS_UNICODE (c))
+ {
+ /* Append a Unicode character. */
+
+ /* Switch from multibyte character mode to Unicode character mode. */
+ mixed_string_buffer_flush_curr_buffer (bp, line_number);
+
+ /* Test whether this character and the previous one form a Unicode
+ surrogate character pair. */
+ if (bp->utf16_surr != 0
+ && (c >= UNICODE (0xdc00) && c < UNICODE (0xe000)))
+ {
+ unsigned short utf16buf[2];
+ ucs4_t uc;
+
+ utf16buf[0] = bp->utf16_surr;
+ utf16buf[1] = UNICODE_VALUE (c);
+ if (u16_mbtouc (&uc, utf16buf, 2) != 2)
+ abort ();
+
+ mixed_string_buffer_append_unicode (bp, uc);
+ bp->utf16_surr = 0;
+ }
+ else
+ {
+ mixed_string_buffer_flush_utf16_surr (bp);
+
+ if (c >= UNICODE (0xd800) && c < UNICODE (0xdc00))
+ bp->utf16_surr = UNICODE_VALUE (c);
+ else if (c >= UNICODE (0xdc00) && c < UNICODE (0xe000))
+ {
+ /* A half surrogate is invalid, therefore use U+FFFD instead. */
+ mixed_string_buffer_append_unicode (bp, 0xfffd);
+ }
+ else
+ mixed_string_buffer_append_unicode (bp, UNICODE_VALUE (c));
+ }
+ }
+ else
+ {
+ /* Append a single byte. */
+
+ /* Switch from Unicode character mode to multibyte character mode. */
+ mixed_string_buffer_flush_utf16_surr (bp);
+
+ /* When a newline is seen, convert the accumulated multibyte sequence.
+ This ensures a correct line number in the error message in case of
+ a conversion error. The "- 1" is to account for the newline. */
+ if (c == '\n')
+ mixed_string_buffer_flush_curr_buffer (bp, line_number - 1);
+
+ mixed_string_buffer_append_byte (bp, (unsigned char) c);
+ }
+}
+
+/* Return the string buffer's contents. */
+static char *
+mixed_string_buffer_result (struct mixed_string_buffer *bp)
+{
+ /* Flush all into bp->utf8_buffer. */
+ mixed_string_buffer_flush_utf16_surr (bp);
+ mixed_string_buffer_flush_curr_buffer (bp, line_number);
+ /* NUL-terminate it. */
+ mixed_string_buffer_append_unicode_grow (bp, 1);
+ bp->utf8_buffer[bp->utf8_buflen] = '\0';
+ /* Return it. */
+ return bp->utf8_buffer;
+}
+
+/* Free the memory pointed to by a 'struct mixed_string_buffer'. */
+static inline void
+free_mixed_string_buffer (struct mixed_string_buffer *bp)
+{
+ free (bp->utf8_buffer);
+ free (bp->curr_buffer);
+}
+
+
+/* ========================== Reading of tokens. ========================== */
+
+
+enum token_type_ty
+{
+ token_type_eof,
+ token_type_lparen, /* ( */
+ token_type_rparen, /* ) */
+ token_type_comma, /* , */
+ token_type_lbracket, /* [ */
+ token_type_rbracket, /* ] */
+ token_type_regexp, /* /.../ */
+ token_type_operator, /* + - * / % . < > = ~ ! | & ? : ^ */
+ token_type_string, /* "abc", 'abc' */
+ token_type_symbol, /* symbol, number */
+ token_type_other /* misc. operator */
+};
+typedef enum token_type_ty token_type_ty;
+
+typedef struct token_ty token_ty;
+struct token_ty
+{
+ token_type_ty type;
+ char *string; /* for token_type_string, token_type_symbol */
+ refcounted_string_list_ty *comment; /* for token_type_string */
+ int line_number;
+ int operator; /* for token_type_operator */
+};
+
+/* Javascript provides strings with either double or single quotes.
+ "abc" or 'abc'
+ Both may contain special sequences after backslash:
+ \\, \", \', \a\b\f\n\r\t\v
+ Special characters from ascii range are selected by on of the following
+ octal or hexadecimal sequences:
+ \oOO \xXX
+ Any unicode point can be entered using the sequence \uNNNN or \uNNNNNNNN
+ */
+
+static int
+phase7_getuc (int quote_char,
+ bool interpret_ansic, bool interpret_unicode,
+ unsigned int *backslash_counter)
+{
+ int c;
+
+ for (;;)
+ {
+ /* Use phase 2, because phase 3 elides comments. */
+ c = phase2_getc ();
+
+ if (c == UEOF)
+ return P7_EOF;
+
+ if (c == quote_char && (interpret_ansic || (*backslash_counter & 1) == 0))
+ {
+ return P7_STRING_END;
+ }
+
+ if (c == '\n')
+ {
+ phase2_ungetc (c);
+ error_with_progname = false;
+ error (0, 0, _("%s:%d: warning: unterminated string"),
+ logical_file_name, line_number);
+ error_with_progname = true;
+ return P7_STRING_END;
+ }
+
+ if (c != '\\')
+ {
+ *backslash_counter = 0;
+ return UNICODE (c);
+ }
+
+ /* Backslash handling. */
+
+ if (!interpret_ansic && !interpret_unicode)
+ {
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+
+ /* Dispatch according to the character following the backslash. */
+ c = phase2_getc ();
+ if (c == UEOF)
+ {
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+
+ if (interpret_ansic)
+ switch (c)
+ {
+ case '\n':
+ continue;
+ case '\\':
+ ++*backslash_counter;
+ return UNICODE (c);
+ case '\'': case '"':
+ *backslash_counter = 0;
+ return UNICODE (c);
+ case 'a':
+ *backslash_counter = 0;
+ return UNICODE ('\a');
+ case 'b':
+ *backslash_counter = 0;
+ return UNICODE ('\b');
+ case 'f':
+ *backslash_counter = 0;
+ return UNICODE ('\f');
+ case 'n':
+ *backslash_counter = 0;
+ return UNICODE ('\n');
+ case 'r':
+ *backslash_counter = 0;
+ return UNICODE ('\r');
+ case 't':
+ *backslash_counter = 0;
+ return UNICODE ('\t');
+ case 'v':
+ *backslash_counter = 0;
+ return UNICODE ('\v');
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7':
+ {
+ int n = c - '0';
+
+ c = phase2_getc ();
+ if (c != UEOF)
+ {
+ if (c >= '0' && c <= '7')
+ {
+ n = (n << 3) + (c - '0');
+ c = phase2_getc ();
+ if (c != UEOF)
+ {
+ if (c >= '0' && c <= '7')
+ n = (n << 3) + (c - '0');
+ else
+ phase2_ungetc (c);
+ }
+ }
+ else
+ phase2_ungetc (c);
+ }
+ *backslash_counter = 0;
+ if (interpret_unicode)
+ return UNICODE (n);
+ else
+ return (unsigned char) n;
+ }
+ case 'x':
+ {
+ int c1 = phase2_getc ();
+ int n1;
+
+ if (c1 >= '0' && c1 <= '9')
+ n1 = c1 - '0';
+ else if (c1 >= 'A' && c1 <= 'F')
+ n1 = c1 - 'A' + 10;
+ else if (c1 >= 'a' && c1 <= 'f')
+ n1 = c1 - 'a' + 10;
+ else
+ n1 = -1;
+
+ if (n1 >= 0)
+ {
+ int c2 = phase2_getc ();
+ int n2;
+
+ if (c2 >= '0' && c2 <= '9')
+ n2 = c2 - '0';
+ else if (c2 >= 'A' && c2 <= 'F')
+ n2 = c2 - 'A' + 10;
+ else if (c2 >= 'a' && c2 <= 'f')
+ n2 = c2 - 'a' + 10;
+ else
+ n2 = -1;
+
+ if (n2 >= 0)
+ {
+ int n = (n1 << 4) + n2;
+ *backslash_counter = 0;
+ if (interpret_unicode)
+ return UNICODE (n);
+ else
+ return (unsigned char) n;
+ }
+
+ phase2_ungetc (c2);
+ }
+ phase2_ungetc (c1);
+ phase2_ungetc (c);
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+ }
+
+ if (interpret_unicode)
+ {
+ if (c == 'u')
+ {
+ unsigned char buf[4];
+ unsigned int n = 0;
+ int i;
+
+ for (i = 0; i < 4; i++)
+ {
+ int c1 = phase2_getc ();
+
+ if (c1 >= '0' && c1 <= '9')
+ n = (n << 4) + (c1 - '0');
+ else if (c1 >= 'A' && c1 <= 'F')
+ n = (n << 4) + (c1 - 'A' + 10);
+ else if (c1 >= 'a' && c1 <= 'f')
+ n = (n << 4) + (c1 - 'a' + 10);
+ else
+ {
+ phase2_ungetc (c1);
+ while (--i >= 0)
+ phase2_ungetc (buf[i]);
+ phase2_ungetc (c);
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+
+ buf[i] = c1;
+ }
+ *backslash_counter = 0;
+ return UNICODE (n);
+ }
+
+ if (interpret_ansic)
+ {
+ if (c == 'U')
+ {
+ unsigned char buf[8];
+ unsigned int n = 0;
+ int i;
+
+ for (i = 0; i < 8; i++)
+ {
+ int c1 = phase2_getc ();
+
+ if (c1 >= '0' && c1 <= '9')
+ n = (n << 4) + (c1 - '0');
+ else if (c1 >= 'A' && c1 <= 'F')
+ n = (n << 4) + (c1 - 'A' + 10);
+ else if (c1 >= 'a' && c1 <= 'f')
+ n = (n << 4) + (c1 - 'a' + 10);
+ else
+ {
+ phase2_ungetc (c1);
+ while (--i >= 0)
+ phase2_ungetc (buf[i]);
+ phase2_ungetc (c);
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+
+ buf[i] = c1;
+ }
+ if (n < 0x110000)
+ {
+ *backslash_counter = 0;
+ return UNICODE (n);
+ }
+
+ error_with_progname = false;
+ error (0, 0, _("%s:%d: warning: invalid Unicode character"),
+ logical_file_name, line_number);
+ error_with_progname = true;
+
+ while (--i >= 0)
+ phase2_ungetc (buf[i]);
+ phase2_ungetc (c);
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+
+ if (c == 'N')
+ {
+ int c1 = phase2_getc ();
+ if (c1 == '{')
+ {
+ unsigned char buf[UNINAME_MAX + 1];
+ int i;
+ unsigned int n;
+
+ for (i = 0; i < UNINAME_MAX; i++)
+ {
+ int c2 = phase2_getc ();
+ if (!(c2 >= ' ' && c2 <= '~'))
+ {
+ phase2_ungetc (c2);
+ while (--i >= 0)
+ phase2_ungetc (buf[i]);
+ phase2_ungetc (c1);
+ phase2_ungetc (c);
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+ if (c2 == '}')
+ break;
+ buf[i] = c2;
+ }
+ buf[i] = '\0';
+
+ n = unicode_name_character ((char *) buf);
+ if (n != UNINAME_INVALID)
+ {
+ *backslash_counter = 0;
+ return UNICODE (n);
+ }
+
+ phase2_ungetc ('}');
+ while (--i >= 0)
+ phase2_ungetc (buf[i]);
+ }
+ phase2_ungetc (c1);
+ phase2_ungetc (c);
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+ }
+ }
+
+ phase2_ungetc (c);
+ ++*backslash_counter;
+ return UNICODE ('\\');
+ }
+}
+
+
+/* Combine characters into tokens. Discard whitespace except newlines at
+ the end of logical lines. */
+
+/* Number of pending open parentheses/braces/brackets. */
+static int open_pbb;
+
+static token_ty phase5_pushback[2];
+static int phase5_pushback_length;
+
+static token_type_ty last_token_type = token_type_other;
+
+static void
+phase5_scan_regexp ()
+{
+ int c;
+ /* scan for end of RegExp literal ('/') */
+ for (;;)
+ {
+ c = phase2_getc (); /* must use phase2 as there can't be comments */
+ if (c == '/')
+ break;
+ switch (c)
+ {
+ case UEOF:
+ error_with_progname = false;
+ error (0, 0, _("%s:%d: warning: RegExp literal terminated too early"),
+ logical_file_name, line_number);
+ error_with_progname = true;
+ return;
+
+ case '\n':
+ error_with_progname = false;
+ error (0, 0, _("%s:%d: warning: RegExp literal contains newline"),
+ logical_file_name, line_number);
+ error_with_progname = true;
+ return;
+
+ case '\\':
+ {
+ int c2 = phase2_getc ();
+ if (c2 == UEOF)
+ {
+ error_with_progname = false;
+ error (0, 0, _("%s:%d: warning: RegExp literal terminated too early"),
+ logical_file_name, line_number);
+ error_with_progname = true;
+ return;
+ }
+ }
+ break;
+
+ default:
+ continue;
+ }
+ }
+ /* scan for modifier flags (ECMA-262 5th section 15.10.4.1) */
+ c = phase2_getc ();
+ if (c == 'g' || c == 'i' || c == 'm')
+ {
+ return;
+ }
+ else
+ {
+ phase2_ungetc (c);
+ }
+}
+
+static void
+phase5_get (token_ty *tp)
+{
+ int c;
+
+ if (phase5_pushback_length)
+ {
+ *tp = phase5_pushback[--phase5_pushback_length];
+ last_token_type = tp->type;
+ return;
+ }
+
+ for (;;)
+ {
+ tp->line_number = line_number;
+ c = phase3_getc ();
+
+ switch (c)
+ {
+ case UEOF:
+ tp->type = last_token_type = token_type_eof;
+ return;
+
+ case ' ':
+ case '\t':
+ case '\f':
+ /* Ignore whitespace and comments. */
+ continue;
+
+ case '\n':
+ if (last_non_comment_line > last_comment_line)
+ savable_comment_reset ();
+ /* Ignore newline if and only if it is used for implicit line
+ joining. */
+ if (open_pbb > 0)
+ continue;
+ tp->type = last_token_type = token_type_other;
+ return;
+ }
+
+ last_non_comment_line = tp->line_number;
+
+ switch (c)
+ {
+ case '.':
+ {
+ int c1 = phase3_getc ();
+ phase3_ungetc (c1);
+ if (!(c1 >= '0' && c1 <= '9'))
+ {
+
+ tp->type = last_token_type = token_type_other;
+ return;
+ }
+ }
+ /* FALLTHROUGH */
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
+ case 'M': case 'N': case 'O': case 'P': case 'Q':
+ case 'S': case 'T': case 'V': case 'W': case 'X':
+ case 'Y': case 'Z':
+ case '_':
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
+ case 'm': case 'n': case 'o': case 'p': case 'q':
+ case 's': case 't': case 'v': case 'w': case 'x':
+ case 'y': case 'z':
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ symbol:
+ /* Symbol, or part of a number. */
+ {
+ static char *buffer;
+ static int bufmax;
+ int bufpos;
+
+ bufpos = 0;
+ for (;;)
+ {
+ if (bufpos >= bufmax)
+ {
+ bufmax = 2 * bufmax + 10;
+ buffer = xrealloc (buffer, bufmax);
+ }
+ buffer[bufpos++] = c;
+ c = phase3_getc ();
+ switch (c)
+ {
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
+ case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
+ case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
+ case 'Y': case 'Z':
+ case '_':
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
+ case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
+ case 's': case 't': case 'u': case 'v': case 'w': case 'x':
+ case 'y': case 'z':
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ continue;
+ default:
+ phase3_ungetc (c);
+ break;
+ }
+ break;
+ }
+ if (bufpos >= bufmax)
+ {
+ bufmax = 2 * bufmax + 10;
+ buffer = xrealloc (buffer, bufmax);
+ }
+ buffer[bufpos] = '\0';
+ tp->string = xstrdup (buffer);
+ tp->type = last_token_type = token_type_symbol;
+ return;
+ }
+
+ /* Strings. */
+ {
+ struct mixed_string_buffer literal;
+ int quote_char;
+ bool interpret_ansic;
+ bool interpret_unicode;
+ unsigned int backslash_counter;
+
+ case '"': case '\'':
+ quote_char = c;
+ interpret_ansic = true;
+ interpret_unicode = false;
+ string:
+ lexical_context = lc_string;
+ backslash_counter = 0;
+ /* Start accumulating the string. */
+ init_mixed_string_buffer (&literal, lc_string);
+ for (;;)
+ {
+ int uc = phase7_getuc (quote_char, interpret_ansic,
+ interpret_unicode, &backslash_counter);
+
+ if (uc == P7_EOF || uc == P7_STRING_END)
+ break;
+
+ if (IS_UNICODE (uc))
+ assert (UNICODE_VALUE (uc) >= 0
+ && UNICODE_VALUE (uc) < 0x110000);
+
+ mixed_string_buffer_append (&literal, uc);
+ }
+ tp->string = xstrdup (mixed_string_buffer_result (&literal));
+ free_mixed_string_buffer (&literal);
+ tp->comment = add_reference (savable_comment);
+ lexical_context = lc_outside;
+ tp->type = last_token_type = token_type_string;
+ return;
+ }
+
+ /* Identify operators. The multiple character ones are simply ignored
+ * as they are recognized here and are otherwise not relevant. */
+ /* FALLTHROUGH */
+ case '+': case '-': case '*': /* '/' is not listed here! */
+ case '%': case '<': case '>': case '=':
+ case '~': case '!': case '|': case '&': case '^':
+ case '?': case ':':
+ tp->operator = c;
+ tp->type = last_token_type = token_type_operator;
+ return;
+
+ case '/':
+ /* Either a division operator or the start of a RegExp literal.
+ * If the '/' token is spotted after a symbol it's a division,
+ * otherwise it's a regex */
+ if (last_token_type == token_type_symbol ||
+ last_token_type == token_type_rparen ||
+ last_token_type == token_type_rbracket)
+ {
+ tp->operator = '/';
+ tp->type = last_token_type = token_type_operator;
+ return;
+ }
+ else
+ {
+ phase5_scan_regexp ();
+ tp->type = last_token_type = token_type_other;
+ return;
+ }
+
+ case '(':
+ open_pbb++;
+ tp->type = last_token_type = token_type_lparen;
+ return;
+
+ case ')':
+ if (open_pbb > 0)
+ open_pbb--;
+ tp->type = last_token_type = token_type_rparen;
+ return;
+
+ case ',':
+ tp->type = last_token_type = token_type_comma;
+ return;
+
+ case '[': case '{':
+ open_pbb++;
+ tp->type = last_token_type = (c == '[' ? token_type_lbracket : token_type_other);
+ return;
+
+ case ']': case '}':
+ if (open_pbb > 0)
+ open_pbb--;
+ tp->type = last_token_type = (c == ']' ? token_type_rbracket : token_type_other);
+ return;
+
+ default:
+ /* We could carefully recognize each of the 2 and 3 character
+ operators, but it is not necessary, as we only need to recognize
+ gettext invocations. Don't bother. */
+ tp->type = last_token_type = token_type_other;
+ return;
+ }
+ }
+}
+
+/* Supports only one pushback token. */
+static void
+phase5_unget (token_ty *tp)
+{
+ if (tp->type != token_type_eof)
+ {
+ if (phase5_pushback_length == SIZEOF (phase5_pushback))
+ abort ();
+ phase5_pushback[phase5_pushback_length++] = *tp;
+ }
+}
+
+
+/* Combine adjacent strings to form a single string. Note that the end
+ of a logical line appears as a token of its own, therefore strings that
+ belong to different logical lines will not be concatenated. */
+
+static void
+x_javascript_lex (token_ty *tp)
+{
+ phase5_get (tp);
+ if (tp->type != token_type_string)
+ return;
+ for (;;)
+ {
+ token_ty tmp;
+ size_t len;
+
+ phase5_get (&tmp);
+ if (tmp.type == token_type_operator && tmp.operator == '+')
+ {
+ /* allow concatenation of strings using the plus '+' operator */
+ token_ty tmp2;
+ phase5_get (&tmp2);
+ if (tmp2.type != token_type_string)
+ {
+ phase5_unget (&tmp2);
+ phase5_unget (&tmp);
+ return;
+ }
+ memcpy(&tmp, &tmp2, sizeof(tmp));
+ }
+ else if (tmp.type != token_type_string)
+ {
+ phase5_unget (&tmp);
+ return;
+ }
+ len = strlen (tp->string);
+ tp->string = xrealloc (tp->string, len + strlen (tmp.string) + 1);
+ strcpy (tp->string + len, tmp.string);
+ free (tmp.string);
+ }
+}
+
+
+/* ========================= Extracting strings. ========================== */
+
+
+/* Context lookup table. */
+static flag_context_list_table_ty *flag_context_list_table;
+
+
+/* The file is broken into tokens. Scan the token stream, looking for
+ a keyword, followed by a left paren, followed by a string. When we
+ see this sequence, we have something to remember. We assume we are
+ looking at a valid Javascript program, and leave the complaints about
+ the grammar to the compiler.
+
+ Normal handling: Look for
+ keyword ( ... msgid ... )
+ Plural handling: Look for
+ keyword ( ... msgid ... msgid_plural ... )
+
+ We use recursion because the arguments before msgid or between msgid
+ and msgid_plural can contain subexpressions of the same form. */
+
+
+/* Extract messages until the next balanced closing parenthesis or bracket.
+ Extracted messages are added to MLP.
+ DELIM can be either token_type_rparen or token_type_rbracket, or
+ token_type_eof to accept both.
+ Return true upon eof, false upon closing parenthesis or bracket. */
+static bool
+extract_balanced (message_list_ty *mlp,
+ token_type_ty delim,
+ flag_context_ty outer_context,
+ flag_context_list_iterator_ty context_iter,
+ struct arglist_parser *argparser)
+{
+ /* Current argument number. */
+ int arg = 1;
+ /* 0 when no keyword has been seen. 1 right after a keyword is seen. */
+ int state;
+ /* Parameters of the keyword just seen. Defined only in state 1. */
+ const struct callshapes *next_shapes = NULL;
+ /* Context iterator that will be used if the next token is a '('. */
+ flag_context_list_iterator_ty next_context_iter =
+ passthrough_context_list_iterator;
+ /* Current context. */
+ flag_context_ty inner_context =
+ inherited_context (outer_context,
+ flag_context_list_iterator_advance (&context_iter));
+
+ /* Start state is 0. */
+ state = 0;
+
+ for (;;)
+ {
+ token_ty token;
+
+ x_javascript_lex (&token);
+ switch (token.type)
+ {
+ case token_type_symbol:
+ {
+ void *keyword_value;
+
+ if (hash_find_entry (&keywords, token.string, strlen (token.string),
+ &keyword_value)
+ == 0)
+ {
+ next_shapes = (const struct callshapes *) keyword_value;
+ state = 1;
+ }
+ else
+ state = 0;
+ }
+ next_context_iter =
+ flag_context_list_iterator (
+ flag_context_list_table_lookup (
+ flag_context_list_table,
+ token.string, strlen (token.string)));
+ free (token.string);
+ continue;
+
+ case token_type_lparen:
+ if (extract_balanced (mlp, token_type_rparen,
+ inner_context, next_context_iter,
+ arglist_parser_alloc (mlp,
+ state ? next_shapes : NULL)))
+ {
+ xgettext_current_source_encoding = po_charset_utf8;
+ arglist_parser_done (argparser, arg);
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding;
+ return true;
+ }
+ next_context_iter = null_context_list_iterator;
+ state = 0;
+ continue;
+
+ case token_type_rparen:
+ if (delim == token_type_rparen || delim == token_type_eof)
+ {
+ xgettext_current_source_encoding = po_charset_utf8;
+ arglist_parser_done (argparser, arg);
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding;
+ return false;
+ }
+ next_context_iter = null_context_list_iterator;
+ state = 0;
+ continue;
+
+ case token_type_comma:
+ arg++;
+ inner_context =
+ inherited_context (outer_context,
+ flag_context_list_iterator_advance (
+ &context_iter));
+ next_context_iter = passthrough_context_list_iterator;
+ state = 0;
+ continue;
+
+ case token_type_lbracket:
+ if (extract_balanced (mlp, token_type_rbracket,
+ null_context, null_context_list_iterator,
+ arglist_parser_alloc (mlp, NULL)))
+ {
+ xgettext_current_source_encoding = po_charset_utf8;
+ arglist_parser_done (argparser, arg);
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding;
+ return true;
+ }
+ next_context_iter = null_context_list_iterator;
+ state = 0;
+ continue;
+
+ case token_type_rbracket:
+ if (delim == token_type_rbracket || delim == token_type_eof)
+ {
+ xgettext_current_source_encoding = po_charset_utf8;
+ arglist_parser_done (argparser, arg);
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding;
+ return false;
+ }
+ next_context_iter = null_context_list_iterator;
+ state = 0;
+ continue;
+
+ case token_type_string:
+ {
+ lex_pos_ty pos;
+ pos.file_name = logical_file_name;
+ pos.line_number = token.line_number;
+
+ xgettext_current_source_encoding = po_charset_utf8;
+ if (extract_all)
+ remember_a_message (mlp, NULL, token.string, inner_context,
+ &pos, NULL, token.comment);
+ else
+ arglist_parser_remember (argparser, arg, token.string,
+ inner_context,
+ pos.file_name, pos.line_number,
+ token.comment);
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding;
+ }
+ drop_reference (token.comment);
+ next_context_iter = null_context_list_iterator;
+ state = 0;
+ continue;
+
+ case token_type_eof:
+ xgettext_current_source_encoding = po_charset_utf8;
+ arglist_parser_done (argparser, arg);
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding;
+ return true;
+
+ /* FALLTHROUGH */
+ case token_type_regexp:
+ case token_type_operator:
+ case token_type_other:
+ next_context_iter = null_context_list_iterator;
+ state = 0;
+ continue;
+
+ default:
+ abort ();
+ }
+ }
+}
+
+
+void
+extract_javascript (FILE *f,
+ const char *real_filename, const char *logical_filename,
+ flag_context_list_table_ty *flag_table,
+ msgdomain_list_ty *mdlp)
+{
+ message_list_ty *mlp = mdlp->item[0]->messages;
+
+ fp = f;
+ real_file_name = real_filename;
+ logical_file_name = xstrdup (logical_filename);
+ line_number = 1;
+
+ lexical_context = lc_outside;
+
+ last_comment_line = -1;
+ last_non_comment_line = -1;
+
+ xgettext_current_file_source_encoding = xgettext_global_source_encoding;
+#if HAVE_ICONV
+ xgettext_current_file_source_iconv = xgettext_global_source_iconv;
+#endif
+
+ xgettext_current_source_encoding = xgettext_current_file_source_encoding;
+#if HAVE_ICONV
+ xgettext_current_source_iconv = xgettext_current_file_source_iconv;
+#endif
+
+ continuation_or_nonblank_line = false;
+
+ open_pbb = 0;
+
+ flag_context_list_table = flag_table;
+
+ init_keywords ();
+
+ /* Eat tokens until eof is seen. When extract_balanced returns
+ due to an unbalanced closing parenthesis, just restart it. */
+ while (!extract_balanced (mlp, token_type_eof,
+ null_context, null_context_list_iterator,
+ arglist_parser_alloc (mlp, NULL)))
+ ;
+
+ fp = NULL;
+ real_file_name = NULL;
+ logical_file_name = NULL;
+ line_number = 0;
+}
diff --git c/gettext-tools/src/x-javascript.h i/gettext-tools/src/x-javascript.h
new file mode 100644
index 0000000..d668136
--- /dev/null
+++ i/gettext-tools/src/x-javascript.h
@@ -0,0 +1,52 @@
+/* xgettext Python backend.
+ Copyright (C) 2002-2003, 2006 Free Software Foundation, Inc.
+ This file was written by Andreas Stricker <[email protected]>, 2010.
+ It's based on x-python from Bruno Haible.
+
+ 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 <http://www.gnu.org/licenses/>. */
+
+
+#include <stdio.h>
+
+#include "message.h"
+#include "xgettext.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define EXTENSIONS_JAVASCRIPT \
+ { "js", "JavaScript" }, \
+
+#define SCANNERS_JAVASCRIPT \
+ { "JavaScript", extract_javascript, \
+ &flag_table_javascript, &formatstring_javascript, NULL }, \
+
+/* Scan a Python file and add its translatable strings to mdlp. */
+extern void extract_javascript (FILE *fp, const char *real_filename,
+ const char *logical_filename,
+ flag_context_list_table_ty *flag_table,
+ msgdomain_list_ty *mdlp);
+
+extern void x_javascript_keyword (const char *keyword);
+extern void x_javascript_extract_all (void);
+
+extern void init_flag_table_javascript (void);
+
+
+#ifdef __cplusplus
+}
+#endif
diff --git c/gettext-tools/src/xgettext.c i/gettext-tools/src/xgettext.c
index eb3271f..c4b39db 100644
--- c/gettext-tools/src/xgettext.c
+++ i/gettext-tools/src/xgettext.c
@@ -82,6 +82,7 @@
#include "x-scheme.h"
#include "x-smalltalk.h"
#include "x-java.h"
+#include "x-javascript.h"
#include "x-properties.h"
#include "x-csharp.h"
#include "x-awk.h"
@@ -154,6 +155,7 @@ static flag_context_list_table_ty flag_table_elisp;
static flag_context_list_table_ty flag_table_librep;
static flag_context_list_table_ty flag_table_scheme;
static flag_context_list_table_ty flag_table_java;
+static flag_context_list_table_ty flag_table_javascript;
static flag_context_list_table_ty flag_table_csharp;
static flag_context_list_table_ty flag_table_awk;
static flag_context_list_table_ty flag_table_ycp;
@@ -325,6 +327,7 @@ main (int argc, char *argv[])
init_flag_table_librep ();
init_flag_table_scheme ();
init_flag_table_java ();
+ init_flag_table_javascript ();
init_flag_table_csharp ();
init_flag_table_awk ();
init_flag_table_ycp ();
@@ -349,6 +352,7 @@ main (int argc, char *argv[])
x_librep_extract_all ();
x_scheme_extract_all ();
x_java_extract_all ();
+ x_javascript_extract_all ();
x_csharp_extract_all ();
x_awk_extract_all ();
x_tcl_extract_all ();
@@ -426,6 +430,7 @@ main (int argc, char *argv[])
x_librep_keyword (optarg);
x_scheme_keyword (optarg);
x_java_keyword (optarg);
+ x_javascript_keyword (optarg);
x_csharp_keyword (optarg);
x_awk_keyword (optarg);
x_tcl_keyword (optarg);
@@ -856,8 +861,9 @@ Choice of input file language:\n"));
-L, --language=NAME recognise the specified language\n\
(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, Glade)\n"));
+ JavaProperties, JavaScript, C#, awk, YCP, Tcl,\n\
+ Perl, PHP, GCC-source, NXStringTable, RST,\n\
+ Glade)\n"));
printf (_("\
-C, --c++ shorthand for --language=C++\n"));
printf (_("\
@@ -1701,6 +1707,11 @@ xgettext_record_flag (const char *optionstring)
name_start, name_end,
argnum, value, pass);
break;
+ case format_javascript:
+ flag_context_list_table_insert (&flag_table_javascript, 0,
+ name_start, name_end,
+ argnum, value, pass);
+ break;
case format_csharp:
flag_context_list_table_insert (&flag_table_csharp, 0,
name_start, name_end,
@@ -3172,6 +3183,7 @@ language_to_extractor (const char *name)
SCANNERS_SCHEME
SCANNERS_SMALLTALK
SCANNERS_JAVA
+ SCANNERS_JAVASCRIPT
SCANNERS_PROPERTIES
SCANNERS_CSHARP
SCANNERS_AWK
@@ -3255,6 +3267,7 @@ extension_to_language (const char *extension)
EXTENSIONS_SCHEME
EXTENSIONS_SMALLTALK
EXTENSIONS_JAVA
+ EXTENSIONS_JAVASCRIPT
EXTENSIONS_PROPERTIES
EXTENSIONS_CSHARP
EXTENSIONS_AWK
diff --git c/gettext-tools/tests/Makefile.am i/gettext-tools/tests/Makefile.am
index f875913..5797104 100644
--- c/gettext-tools/tests/Makefile.am
+++ i/gettext-tools/tests/Makefile.am
@@ -82,6 +82,7 @@ TESTS = gettext-1 gettext-2 gettext-3 gettext-4 gettext-5 gettext-6 gettext-7 \
xgettext-glade-1 xgettext-glade-2 xgettext-glade-3 xgettext-glade-4 \
xgettext-java-1 xgettext-java-2 xgettext-java-3 xgettext-java-4 \
xgettext-java-5 xgettext-java-6 xgettext-java-7 \
+ xgettext-javascript-1 xgettext-javascript-2 xgettext-javascript-3 \
xgettext-librep-1 xgettext-librep-2 \
xgettext-lisp-1 xgettext-lisp-2 \
xgettext-objc-1 xgettext-objc-2 \
diff --git c/gettext-tools/tests/Makefile.in i/gettext-tools/tests/Makefile.in
index bb59f1f..124a125 100644
--- c/gettext-tools/tests/Makefile.in
+++ i/gettext-tools/tests/Makefile.in
@@ -1595,6 +1595,7 @@ TESTS = gettext-1 gettext-2 gettext-3 gettext-4 gettext-5 gettext-6 gettext-7 \
xgettext-glade-1 xgettext-glade-2 xgettext-glade-3 xgettext-glade-4 \
xgettext-java-1 xgettext-java-2 xgettext-java-3 xgettext-java-4 \
xgettext-java-5 xgettext-java-6 xgettext-java-7 \
+ xgettext-javascript-1 xgettext-javascript-2 xgettext-javascript-3 \
xgettext-librep-1 xgettext-librep-2 \
xgettext-lisp-1 xgettext-lisp-2 \
xgettext-objc-1 xgettext-objc-2 \
diff --git c/gettext-tools/tests/xgettext-javascript-1 i/gettext-tools/tests/xgettext-javascript-1
new file mode 100644
index 0000000..e99523d
--- /dev/null
+++ i/gettext-tools/tests/xgettext-javascript-1
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+# Test of Javascript support.
+
+tmpfiles=""
+trap 'rm -fr $tmpfiles' 1 2 3 15
+
+tmpfiles="$tmpfiles xg-js-1.js"
+cat <<\EOF > xg-js-1.js
+var s1 = "Simple string, no gettext needed",
+ s2 = _("Extract this first string");
+function foo(a) {
+ var s3 = "Prefix _(" + _("Extract this second string") + ") Postfix";
+}
+if (document.getElementsById("foo")[0].innerHTML == _("Extract this thirth string")) {
+ /* _("This is a comment and must not be extracted!") */
+}
+EOF
+
+tmpfiles="$tmpfiles xg-js-1.err xg-js-1.tmp xg-js-1.pot"
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --add-comments --no-location -o xg-js-1.tmp xg-js-1.js 2>xg-js-1.err
+test $? = 0 || { cat xg-js-1.err; rm -fr $tmpfiles; exit 1; }
+# Don't simplify this to "grep ... < xg-js-1.tmp", otherwise OpenBSD 4.0 grep
+# only outputs "Binary file (standard input) matches".
+cat xg-js-1.tmp | grep -v 'POT-Creation-Date' | LC_ALL=C tr -d '\r' > xg-js-1.pot
+
+tmpfiles="$tmpfiles xg-js-1.ok"
+cat <<\EOF > xg-js-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 <[email protected]>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "Extract this first string"
+msgstr ""
+
+msgid "Extract this second string"
+msgstr ""
+
+msgid "Extract this thirth string"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-js-1.ok xg-js-1.pot
+result=$?
+
+rm -fr $tmpfiles
+
+exit $result
diff --git c/gettext-tools/tests/xgettext-javascript-2 i/gettext-tools/tests/xgettext-javascript-2
new file mode 100644
index 0000000..9766646
--- /dev/null
+++ i/gettext-tools/tests/xgettext-javascript-2
@@ -0,0 +1,91 @@
+#!/bin/sh
+
+# Test of Javascript support.
+# Playing with regex and division operator
+
+tmpfiles=""
+trap 'rm -fr $tmpfiles' 1 2 3 15
+
+tmpfiles="$tmpfiles xg-js-2.js"
+cat <<\EOF > xg-js-2.js
+// RegExp literals containing string quotes must not desync the parser
+var d = 1 / 2 / 4;
+var s = " x " + /^\d/.match("0815").replace(/[a-z]/g, '@');
+var s1 = /"/.match(_("RegExp test string #1"));
+var s2 = /'/.match(_("RegExp test string #2"));
+var s3 = /['a-b]/.match(_('RegExp test string #3'));
+var s4 = /["a-b]/.match(_('RegExp test string #4'));
+var s5 = /[a-b']/.match(_('RegExp test string #5'));
+var s6 = /[a-b"]/.match(_('RegExp test string #6'));
+var c = 35 / 2 / 8 + _("RegExp test string #7").length / 32.0;
+var sizestr = Math.round(size/1024*factor)/factor+_("RegExp test string #8");
+var cssClassType = attr.type.replace(/^.*\//, _('RegExp test string #9')).replace(/\./g, '-');
+var lookup = lookuptable[idx]/factor+_("RegExp test string #10");
+EOF
+
+tmpfiles="$tmpfiles xg-js-2.err xg-js-2.tmp xg-js-2.pot"
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --add-comments --no-location -o xg-js-2.tmp xg-js-2.js 2>xg-js-2.err
+test $? = 0 || { cat xg-js-2.err; rm -fr $tmpfiles; exit 1; }
+# Don't simplify this to "grep ... < xg-js-2.tmp", otherwise OpenBSD 4.0 grep
+# only outputs "Binary file (standard input) matches".
+cat xg-js-2.tmp | grep -v 'POT-Creation-Date' | LC_ALL=C tr -d '\r' > xg-js-2.pot
+
+tmpfiles="$tmpfiles xg-js-2.ok"
+cat <<\EOF > xg-js-2.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 <[email protected]>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "RegExp test string #1"
+msgstr ""
+
+msgid "RegExp test string #2"
+msgstr ""
+
+msgid "RegExp test string #3"
+msgstr ""
+
+msgid "RegExp test string #4"
+msgstr ""
+
+msgid "RegExp test string #5"
+msgstr ""
+
+msgid "RegExp test string #6"
+msgstr ""
+
+msgid "RegExp test string #7"
+msgstr ""
+
+msgid "RegExp test string #8"
+msgstr ""
+
+msgid "RegExp test string #9"
+msgstr ""
+
+msgid "RegExp test string #10"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-js-2.ok xg-js-2.pot
+result=$?
+
+rm -fr $tmpfiles
+
+exit $result
diff --git c/gettext-tools/tests/xgettext-javascript-3 i/gettext-tools/tests/xgettext-javascript-3
new file mode 100644
index 0000000..44e5366
--- /dev/null
+++ i/gettext-tools/tests/xgettext-javascript-3
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+# Test of Javascript support.
+# Playing with concatenation of string literals withing the gettext function
+
+tmpfiles=""
+trap 'rm -fr $tmpfiles' 1 2 3 15
+
+tmpfiles="$tmpfiles xg-js-2.js"
+cat <<\EOF > xg-js-2.js
+// The usual way to concatenate strings is the plus '+' sign
+var s1 = _("Concatenation #1 " "- String part added");
+var s2 = _('Concatenation #2 ' + '- String part added');
+var s3 = _('Concatenation #3 '
+ '- String part added');
+var s4 = _('Concatenation #4 ' +
+ '- String part added');
+var s5 = _("This" + " whole "
+ + "string" +
+ ' should' + " be " + 'extracted');
+EOF
+
+tmpfiles="$tmpfiles xg-js-2.err xg-js-2.tmp xg-js-2.pot"
+: ${XGETTEXT=xgettext}
+${XGETTEXT} --add-comments --no-location -o xg-js-2.tmp xg-js-2.js 2>xg-js-2.err
+test $? = 0 || { cat xg-js-2.err; rm -fr $tmpfiles; exit 1; }
+# Don't simplify this to "grep ... < xg-js-2.tmp", otherwise OpenBSD 4.0 grep
+# only outputs "Binary file (standard input) matches".
+cat xg-js-2.tmp | grep -v 'POT-Creation-Date' | LC_ALL=C tr -d '\r' > xg-js-2.pot
+
+tmpfiles="$tmpfiles xg-js-2.ok"
+cat <<\EOF > xg-js-2.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 <[email protected]>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "Concatenation #1 - String part added"
+msgstr ""
+
+msgid "Concatenation #2 - String part added"
+msgstr ""
+
+msgid "Concatenation #3 - String part added"
+msgstr ""
+
+msgid "Concatenation #4 - String part added"
+msgstr ""
+
+msgid "This whole string should be extracted"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-js-2.ok xg-js-2.pot
+result=$?
+
+rm -fr $tmpfiles
+
+exit $result
diff --git c/gettext-tools/woe32dll/gettextsrc-exports.c i/gettext-tools/woe32dll/gettextsrc-exports.c
index 44ff5c5..7c9c59b 100644
--- c/gettext-tools/woe32dll/gettextsrc-exports.c
+++ i/gettext-tools/woe32dll/gettextsrc-exports.c
@@ -30,6 +30,7 @@ VARIABLE(formatstring_elisp)
VARIABLE(formatstring_gcc_internal)
VARIABLE(formatstring_gfc_internal)
VARIABLE(formatstring_java)
+VARIABLE(formatstring_javascript)
VARIABLE(formatstring_kde)
VARIABLE(formatstring_librep)
VARIABLE(formatstring_lisp)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment