bug-gettext
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[bug-gettext] [PATCH] Javascript support for gettext-tools


From: Carl Fürstenberg
Subject: [bug-gettext] [PATCH] Javascript support for gettext-tools
Date: Thu, 27 Dec 2012 13:18:32 +0100

From: Andreas Stricker <address@hidden>

It's based on the python scanner. The scanner is added to the build process
and integrates it in the xgettext utility.
---
 gettext-tools/doc/xgettext.texi             |    4 +
 gettext-tools/libgettextpo/Makefile.am      |    1 +
 gettext-tools/src/Makefile.am               |    7 +-
 gettext-tools/src/format-javascript.c       |  671 ++++++++++
 gettext-tools/src/format.c                  |    1 +
 gettext-tools/src/format.h                  |    1 +
 gettext-tools/src/message.c                 |    2 +
 gettext-tools/src/message.h                 |    3 +-
 gettext-tools/src/x-javascript.c            | 1840 +++++++++++++++++++++++++++
 gettext-tools/src/x-javascript.h            |   52 +
 gettext-tools/src/xgettext.c                |   17 +-
 gettext-tools/tests/Makefile.am             |    1 +
 gettext-tools/tests/xgettext-javascript-1   |   64 +
 gettext-tools/tests/xgettext-javascript-2   |   99 ++
 gettext-tools/tests/xgettext-javascript-3   |   73 ++
 gettext-tools/woe32dll/gettextsrc-exports.c |    1 +
 16 files changed, 2831 insertions(+), 6 deletions(-)
 create mode 100644 gettext-tools/src/format-javascript.c
 create mode 100644 gettext-tools/src/x-javascript.c
 create mode 100644 gettext-tools/src/x-javascript.h
 create mode 100755 gettext-tools/tests/xgettext-javascript-1
 create mode 100644 gettext-tools/tests/xgettext-javascript-2
 create mode 100644 gettext-tools/tests/xgettext-javascript-3

diff --git a/gettext-tools/doc/xgettext.texi b/gettext-tools/doc/xgettext.texi
index 4c59f4c..a48bdad 100644
--- a/gettext-tools/doc/xgettext.texi
+++ b/gettext-tools/doc/xgettext.texi
@@ -245,6 +245,10 @@ For PHP: @code{_}, @code{gettext}, @code{dgettext:2}, 
@code{dcgettext:2},
 @item
 For Glade 1: @code{label}, @code{title}, @code{text}, @code{format},
 @code{copyright}, @code{comments}, @code{preview_text}, @code{tooltip}.
+
address@hidden
+For JavaScript: @code{_}, @code{gettext}, @code{dgettext:2}, 
@code{ngettext:1,2}, @code{dngettext:2,3},
address@hidden:1c,2}, @code{dpgettext:2c,3}, @code{npgettext:1c,2,3}, 
@code{dnpgettext:2c,3,4}.
 @end itemize
 
 To disable the default keyword specifications, the option @samp{-k} or
diff --git a/gettext-tools/libgettextpo/Makefile.am 
b/gettext-tools/libgettextpo/Makefile.am
index cf4a928..cee9418 100644
--- a/gettext-tools/libgettextpo/Makefile.am
+++ b/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 a/gettext-tools/src/Makefile.am b/gettext-tools/src/Makefile.am
index 87cc358..829bea3 100644
--- a/gettext-tools/src/Makefile.am
+++ b/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 a/gettext-tools/src/format-javascript.c 
b/gettext-tools/src/format-javascript.c
new file mode 100644
index 0000000..9ae13f9
--- /dev/null
+++ b/gettext-tools/src/format-javascript.c
@@ -0,0 +1,671 @@
+/* JavaScript format strings.
+   Copyright (C) 2001-2004, 2006-2009 Free Software Foundation, Inc.
+   Written by Andreas Stricker <address@hidden>, 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 (equality
+      ? spec1->unnamed_arg_count != spec2->unnamed_arg_count
+      : spec1->unnamed_arg_count < spec2->unnamed_arg_count)
+        {
+          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 a/gettext-tools/src/format.c b/gettext-tools/src/format.c
index e6c5de9..22fc8c2 100644
--- a/gettext-tools/src/format.c
+++ b/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 a/gettext-tools/src/format.h b/gettext-tools/src/format.h
index 60f0adc..679cd9a 100644
--- a/gettext-tools/src/format.h
+++ b/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 a/gettext-tools/src/message.c b/gettext-tools/src/message.c
index 5162b06..e57ddab 100644
--- a/gettext-tools/src/message.c
+++ b/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 a/gettext-tools/src/message.h b/gettext-tools/src/message.h
index af9244a..0f200b1 100644
--- a/gettext-tools/src/message.h
+++ b/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 a/gettext-tools/src/x-javascript.c b/gettext-tools/src/x-javascript.c
new file mode 100644
index 0000000..be06537
--- /dev/null
+++ b/gettext-tools/src/x-javascript.c
@@ -0,0 +1,1840 @@
+/* xgettext JavaScript backend.
+   Copyright (C) 2002-2003, 2005-2009 Free Software Foundation, Inc.
+
+   This file was written by Andreas Stricker <address@hidden>, 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 ("pgettext:1c,2");
+      x_javascript_keyword ("dpgettext:2c,3");
+      x_javascript_keyword ("npgettext:1c,2,3");
+      x_javascript_keyword ("dnpgettext:2c,3,4");
+      x_javascript_keyword ("_");
+      default_keywords = false;
+    }
+}
+
+void
+init_flag_table_javascript ()
+{
+  /* jQuery sprintf (https://github.com/azatoth/jquery-sprintf)
+   * A.K.A. Shameless plug */
+  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");
+  xgettext_record_flag ("pgettext:2:pass-javascript-format");
+  xgettext_record_flag ("dpgettext:3:pass-javascript-format");
+  xgettext_record_flag ("npgettext:2:pass-javascript-format");
+  xgettext_record_flag ("npgettext:3:pass-javascript-format");
+  xgettext_record_flag ("dnpgettext:3:pass-javascript-format");
+  xgettext_record_flag ("dnpgettext:4:pass-javascript-format");
+
+  xgettext_record_flag ("$.printf:1:javascript-format");
+  xgettext_record_flag ("$.vprintf:1:javascript-format");
+  xgettext_record_flag ("$.sprintf:1:javascript-format");
+  xgettext_record_flag ("$.vsprintf:1:javascript-format");
+
+  /* NOTE!
+   *
+   * There are many dirrerent string format libraries/plugins/snippets for 
javascript.
+   * While we can't support them all, we could hopefully find the major ones.
+   * jQuery sprintf above is a shameless plug from author himself :-P
+   * */
+
+}
+
+
+/* ======================== 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 char last_token_symbol[512];
+
+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 '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':
+        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);
+            strncpy(last_token_symbol, buffer, sizeof(last_token_symbol) - 1);
+            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,
+                  * unless the symbol is found after a return, as it is a 
regex then*/
+          if ((
+              last_token_type == token_type_symbol &&
+              strncmp("return", last_token_symbol, 6) != 0
+              ) ||
+              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 a/gettext-tools/src/x-javascript.h b/gettext-tools/src/x-javascript.h
new file mode 100644
index 0000000..1c64a3d
--- /dev/null
+++ b/gettext-tools/src/x-javascript.h
@@ -0,0 +1,52 @@
+/* xgettext Javascript backend.
+   Copyright (C) 2002-2003, 2006 Free Software Foundation, Inc.
+   This file was written by Andreas Stricker <address@hidden>, 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 Javascript 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 a/gettext-tools/src/xgettext.c b/gettext-tools/src/xgettext.c
index eb3271f..c4b39db 100644
--- a/gettext-tools/src/xgettext.c
+++ b/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 a/gettext-tools/tests/Makefile.am b/gettext-tools/tests/Makefile.am
index f875913..5797104 100644
--- a/gettext-tools/tests/Makefile.am
+++ b/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 a/gettext-tools/tests/xgettext-javascript-1 
b/gettext-tools/tests/xgettext-javascript-1
new file mode 100755
index 0000000..e99523d
--- /dev/null
+++ b/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 <address@hidden>, 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 <address@hidden>\n"
+"Language-Team: LANGUAGE <address@hidden>\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 a/gettext-tools/tests/xgettext-javascript-2 
b/gettext-tools/tests/xgettext-javascript-2
new file mode 100644
index 0000000..fd550b7
--- /dev/null
+++ b/gettext-tools/tests/xgettext-javascript-2
@@ -0,0 +1,99 @@
+#!/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");
+
+function doit(a){
+       return /\./.match(a);
+}
+doit(_("RegExp test string #11"));
+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 <address@hidden>, 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 <address@hidden>\n"
+"Language-Team: LANGUAGE <address@hidden>\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 ""
+
+msgid "RegExp test string #11"
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} xg-js-2.ok xg-js-2.pot
+result=$?
+
+rm -fr $tmpfiles
+
+exit $result
diff --git a/gettext-tools/tests/xgettext-javascript-3 
b/gettext-tools/tests/xgettext-javascript-3
new file mode 100644
index 0000000..bb55f10
--- /dev/null
+++ b/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 <address@hidden>, 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 <address@hidden>\n"
+"Language-Team: LANGUAGE <address@hidden>\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 a/gettext-tools/woe32dll/gettextsrc-exports.c 
b/gettext-tools/woe32dll/gettextsrc-exports.c
index 44ff5c5..7c9c59b 100644
--- a/gettext-tools/woe32dll/gettextsrc-exports.c
+++ b/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)
-- 
1.7.10.4




reply via email to

[Prev in Thread] Current Thread [Next in Thread]