From: Bruno Haible Date: Sat, 28 Sep 2024 00:09:36 +0000 (+0200) Subject: xgettext: JavaScript: Support customized handling of tagged template literals. X-Git-Tag: v0.23~104 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=967e76ba4f72d6e427ddd3bdca1480a978c4a5a8;p=thirdparty%2Fgettext.git xgettext: JavaScript: Support customized handling of tagged template literals. Reported by Evan Welsh and Philip Chimento at . * gettext-tools/src/str-list.h (string_list_append_move): New declaration. * gettext-tools/src/str-list.c (string_list_append_move): New function. * gettext-tools/src/x-javascript.h (x_javascript_tag): New declaration. * gettext-tools/src/x-javascript.c: Include str-list.h. (tag_step1_fn): New type. (gnome_step1, get_tag_step1_fn): New functions. (struct tag_definition): New type. (tags): New variable. (x_javascript_tag): New function. (struct token_ty): Add fields template_tag, template_parts. (free_token): Update. (struct level_info): New type. (levels): Change element type to 'struct level_info'. (new_level, level_type): Update. (phase5_get, x_javascript_lex): Arrange to collect the parts of a template literal in the last token's template_parts field. (extract_balanced): Handle tagged template literals. * gettext-tools/src/xgettext.c (long_options): Add option --tag. (main): Handle option --tag. (usage): Document ption --tag. * gettext-tools/tests/xgettext-javascript-13: New file. * gettext-tools/tests/Makefile.am (TESTS): Add it. * gettext-tools/doc/gettext.texi (No string concatenation): Mention the tagged template literals as alternative. * gettext-tools/doc/xgettext.texi: Document the --tag option. * gettext-tools/doc/lang-javascript.texi (JavaScript): Mention the tagged template literals syntax. * NEWS: Mention the change. --- diff --git a/NEWS b/NEWS index 2c1410feb..3961ba2e1 100644 --- a/NEWS +++ b/NEWS @@ -16,7 +16,10 @@ Version 0.23 - September 2024 o xgettext now recognizes comments of the form '#; '. - Java: Improved recognition of format strings when the String.formatted method is used. - - JavaScript: xgettext now parses template literals inside JSX correctly. + - JavaScript: + o xgettext now parses template literals inside JSX correctly. + o xgettext has a new option --tag that customizes the behaviour of tagged + template literals. - C#: Strings with embedded expressions (a.k.a. interpolated strings) are now recognized. - awk: String concatenation by juxtaposition is now recognized. diff --git a/gettext-tools/doc/gettext.texi b/gettext-tools/doc/gettext.texi index ebedb5675..88096771f 100644 --- a/gettext-tools/doc/gettext.texi +++ b/gettext-tools/doc/gettext.texi @@ -2424,6 +2424,17 @@ into a statement involving a format string: print ('Replace %s with %s?'.format(object1.name, object2.name)) @end example +@noindent +Specifically in JavaScript, +an alternative is to use a @emph{tagged} template literal: + +@example +print (@var{tag}`Replace $@{object1.name@} with $@{object2.name@}?`) +@end example + +@noindent +and pass an option @samp{--tag=@var{tag}:@var{format}} to @code{xgettext}. + @subheading Format strings with embedded named references Format strings with embedded named references are different: diff --git a/gettext-tools/doc/lang-javascript.texi b/gettext-tools/doc/lang-javascript.texi index c678725ec..3759dca94 100644 --- a/gettext-tools/doc/lang-javascript.texi +++ b/gettext-tools/doc/lang-javascript.texi @@ -24,6 +24,10 @@ gjs @item @code{`abc`} +@c https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals +@item @code{@var{tag}`abc$@{@var{expression}@}def@{@var{expression}@}...`}, +see the description of @samp{--tag} in @ref{xgettext Invocation}. + @end itemize @item gettext shorthand diff --git a/gettext-tools/doc/xgettext.texi b/gettext-tools/doc/xgettext.texi index cb3d3dff9..a6037c11d 100644 --- a/gettext-tools/doc/xgettext.texi +++ b/gettext-tools/doc/xgettext.texi @@ -409,6 +409,22 @@ This option has an effect with most languages, namely C, C++, ObjectiveC, Shell, Python, Lisp, EmacsLisp, librep, Scheme, Guile, Java, C#, awk, YCP, Tcl, Perl, PHP, GCC-source, Lua, JavaScript, Vala. +@item --tag=@var{word}:@var{format} +@opindex --tag@r{, @code{xgettext} option} +Defines the behaviour of tagged template literals with tag @var{word}. +This option has an effect only with language JavaScript. +@* +@var{format} is a symbolic description +of the first step of the JavaScript function named @var{word}, +namely how this function constructs a format string +based on the parts of the template literal. +Currently only one value is supported: @code{javascript-gnome-format}, +which describes the construction of a format string with numbered placeholders +@code{@{0@}}, @code{@{1@}}, @code{@{2@}}, etc. +For example, @code{javascript-gnome-format} transforms the template literal +@code{@var{word}`My name is $@{id.name@} and I am $@{id.age@} years old.`} +into the format string @code{"My name is @{0@} and I am @{1@} years old."}. + @item -T @itemx --trigraphs @opindex -T@r{, @code{xgettext} option} diff --git a/gettext-tools/src/str-list.c b/gettext-tools/src/str-list.c index 4909edc23..afe36d596 100644 --- a/gettext-tools/src/str-list.c +++ b/gettext-tools/src/str-list.c @@ -1,6 +1,5 @@ /* GNU gettext - internationalization aids - Copyright (C) 1995, 1998, 2000-2004, 2006, 2009, 2020 Free Software - Foundation, Inc. + Copyright (C) 1995-2024 Free Software Foundation, Inc. This file was written by Peter Miller @@ -75,6 +74,26 @@ string_list_append (string_list_ty *slp, const char *s) } +/* Append a freshly allocated single string to the end of a list of strings, + transferring its ownership to SLP. */ +void +string_list_append_move (string_list_ty *slp, char *s) +{ + /* Grow the list. */ + if (slp->nitems >= slp->nitems_max) + { + size_t nbytes; + + slp->nitems_max = slp->nitems_max * 2 + 4; + nbytes = slp->nitems_max * sizeof (slp->item[0]); + slp->item = (const char **) xrealloc (slp->item, nbytes); + } + + /* Add the string itself to the end of the list. */ + slp->item[slp->nitems++] = s; +} + + /* Append a single string to the end of a list of strings, unless it is already contained in the list. */ void diff --git a/gettext-tools/src/str-list.h b/gettext-tools/src/str-list.h index a39f65823..e7f8006fb 100644 --- a/gettext-tools/src/str-list.h +++ b/gettext-tools/src/str-list.h @@ -1,6 +1,5 @@ /* GNU gettext - internationalization aids - Copyright (C) 1995-1996, 1998, 2000-2004, 2009, 2020 Free Software - Foundation, Inc. + Copyright (C) 1995-2024 Free Software Foundation, Inc. This file was written by Peter Miller @@ -51,6 +50,10 @@ extern string_list_ty *string_list_alloc (void); /* Append a single string to the end of a list of strings. */ extern void string_list_append (string_list_ty *slp, const char *s); +/* Append a freshly allocated single string to the end of a list of strings, + transferring its ownership to SLP. */ +extern void string_list_append_move (string_list_ty *slp, char *s); + /* Append a single string to the end of a list of strings, unless it is already contained in the list. */ extern void string_list_append_unique (string_list_ty *slp, const char *s); diff --git a/gettext-tools/src/x-javascript.c b/gettext-tools/src/x-javascript.c index 024b7b364..07b6034b3 100644 --- a/gettext-tools/src/x-javascript.c +++ b/gettext-tools/src/x-javascript.c @@ -34,6 +34,7 @@ #include #include "attribute.h" #include "message.h" +#include "str-list.h" #include "rc-str-list.h" #include "xgettext.h" #include "xg-pos.h" @@ -150,6 +151,105 @@ init_flag_table_javascript () } +/* ======================== Tag set customization. ======================== */ + +/* Tagged template literals are described in + . + + A tagged template literal looks like this in the source code: + TAG`part0 ${expression 1} part1 ${expression 2} ... ${expression N} partN` + + A tag, immediately before a template literal, denotes a function that takes + as arguments: + - A list of the N+1 parts of the template literal, + - The N values of the N expressions between these parts. + + In our use case, the tag function is supposed to + 1. Convert the N+1 parts to a format string. + 2. Look up the translation of this format string. (It is supposed to + accept the same number of arguments.) + 3. Call a formatting facility that substitutes the N values into the + translated format string. + The type of format string is not fixed. */ + +/* Type of a C function that implements step 1 of what a tag function does. + It takes a non-empty string_list_ty as argument and returns a freshly + allocated string. */ +typedef char * (*tag_step1_fn) (string_list_ty *parts); + +/* Tag step 1 function that produces a format string with placeholders + {0}, {1}, {2}, etc. */ +static char * +gnome_step1 (string_list_ty *parts) +{ + size_t n = parts->nitems - 1; + string_list_ty pieces; + unsigned long i; + + pieces.nitems = 2 * n + 1; + pieces.nitems_max = pieces.nitems; + pieces.item = XNMALLOC (pieces.nitems, const char *); + for (i = 0; i <= n; i++) + { + pieces.item[2 * i] = parts->item[i]; + if (i < n) + pieces.item[2 * i + 1] = xasprintf ("{%lu}", i); + } + + char *result = string_list_concat (&pieces); + + for (i = 0; i < n; i++) + free ((char *) pieces.item[2 * i + 1]); + + return result; +} + +/* Returns the tag step 1 function for a given format, or NULL if that format + is unknown. */ +static tag_step1_fn +get_tag_step1_fn (const char *format) +{ + if (strcmp (format, "javascript-gnome-format") == 0) + return gnome_step1; + /* ... More formats can be added here ... */ + return NULL; +} + +/* Information associated with a tag. */ +struct tag_definition +{ + const char *format; + tag_step1_fn step1_fn; +}; + +/* Mapping tag -> format. */ +static hash_table tags; + +void +x_javascript_tag (const char *name) +{ + const char *colon = strchr (name, ':'); + if (colon != NULL) + { + const char *format = colon + 1; + tag_step1_fn step1_fn = get_tag_step1_fn (format); + if (step1_fn != NULL) + { + /* Heap-allocate a 'struct tag_definition' */ + struct tag_definition *def = XMALLOC (struct tag_definition); + def->format = xstrdup (format); + def->step1_fn = step1_fn; + + if (tags.table == NULL) + hash_init (&tags, 10); + + /* Insert it in the TAGS table. */ + hash_set_value (&tags, name, colon - name, def); + } + } +} + + /* ======================== Reading of characters. ======================== */ /* The input file stream. */ @@ -699,9 +799,15 @@ typedef struct token_ty token_ty; struct token_ty { token_type_ty type; - char *string; /* for token_type_symbol, token_type_keyword */ - mixed_string_ty *mixed_string; /* for token_type_string, token_type_template */ - refcounted_string_list_ty *comment; /* for token_type_string, token_type_template */ + char *template_tag; /* for token_type_template, token_type_ltemplate, + token_type_rtemplate */ + char *string; /* for token_type_symbol, token_type_keyword */ + mixed_string_ty *mixed_string; /* for token_type_string, token_type_template, + token_type_ltemplate, token_type_mtemplate, + token_type_rtemplate */ + string_list_ty *template_parts; /* for token_type_rtemplate */ + refcounted_string_list_ty *comment; /* for token_type_string, token_type_template, + token_type_ltemplate, token_type_rtemplate */ int line_number; }; @@ -710,13 +816,23 @@ struct token_ty static inline void free_token (token_ty *tp) { + if (tp->type == token_type_template || tp->type == token_type_ltemplate + || tp->type == token_type_rtemplate) + free (tp->template_tag); if (tp->type == token_type_symbol || tp->type == token_type_keyword) free (tp->string); - if (tp->type == token_type_string || tp->type == token_type_template) - { - mixed_string_free (tp->mixed_string); - drop_reference (tp->comment); - } + if (tp->type == token_type_string || tp->type == token_type_template + /* For these types, tp->mixed_string is already freed earlier, when we + build up the level's template_parts. */ + #if 0 + || tp->type == token_type_ltemplate || tp->type == token_type_mtemplate + || tp->type == token_type_rtemplate + #endif + ) + mixed_string_free (tp->mixed_string); + if (tp->type == token_type_string || tp->type == token_type_template + || tp->type == token_type_ltemplate || tp->type == token_type_rtemplate) + drop_reference (tp->comment); } @@ -1010,8 +1126,15 @@ enum level_ty level_xml_element = 3, level_embedded_js_in_xml = 4 }; +struct level_info +{ + enum level_ty type; + char *template_tag; /* for level_template_literal */ + string_list_ty *template_parts; /* for level_template_literal */ + refcounted_string_list_ty *template_comment; /* for level_template_literal */ +}; /* The level stack. */ -static unsigned char *levels /* = NULL */; +static struct level_info *levels /* = NULL */; /* Number of allocated elements in levels. */ static size_t levels_alloc /* = 0 */; /* Number of currently used elements in levels. */ @@ -1026,16 +1149,17 @@ new_level (enum level_ty l) levels_alloc = 2 * levels_alloc + 1; /* Now level < levels_alloc. */ levels = - (unsigned char *) - xrealloc (levels, levels_alloc * sizeof (unsigned char)); + (struct level_info *) + xrealloc (levels, levels_alloc * sizeof (struct level_info)); } - levels[level++] = l; + levels[level].type = l; + level++; } /* Returns the current level's type, as one of the level_* enum items, or 0 if the level stack is empty. */ #define level_type() \ - (level > 0 ? levels[level - 1] : 0) + (level > 0 ? levels[level - 1].type : 0) /* Parses some XML markup. Returns 0 for an XML comment, @@ -1300,6 +1424,7 @@ phase5_get (token_ty *tp) if (uc == P7_EOF || uc == P7_STRING_END) { + tp->template_tag = NULL; tp->mixed_string = mixed_string_buffer_result (&msb); tp->comment = add_reference (savable_comment); tp->type = last_token_type = token_type_template; @@ -1308,9 +1433,14 @@ phase5_get (token_ty *tp) if (uc == P7_TEMPLATE_START_OF_EXPRESSION) { - mixed_string_buffer_destroy (&msb); + tp->template_tag = NULL; + tp->mixed_string = mixed_string_buffer_result (&msb); + tp->comment = add_reference (savable_comment); tp->type = last_token_type = token_type_ltemplate; new_level (level_template_literal); + levels[level - 1].template_tag = NULL; + levels[level - 1].template_parts = NULL; + levels[level - 1].template_comment = NULL; break; } @@ -1466,23 +1596,51 @@ phase5_get (token_ty *tp) else if (level_type () == level_template_literal) { /* Middle or right part of template literal. */ + struct mixed_string_buffer msb; + + lexical_context = lc_string; + /* Start accumulating the string. */ + mixed_string_buffer_init (&msb, lexical_context, + logical_file_name, line_number); for (;;) { int uc = phase7_getuc ('`'); + /* Keep line_number in sync. */ + msb.line_number = line_number; + if (uc == P7_EOF || uc == P7_STRING_END) { + tp->mixed_string = mixed_string_buffer_result (&msb); tp->type = last_token_type = token_type_rtemplate; + string_list_append_move (levels[level - 1].template_parts, + mixed_string_contents_free1 (tp->mixed_string)); + /* Move info from the current level to the token. */ + tp->template_tag = levels[level - 1].template_tag; + tp->template_parts = levels[level - 1].template_parts; + tp->comment = levels[level - 1].template_comment; level--; break; } if (uc == P7_TEMPLATE_START_OF_EXPRESSION) { + tp->mixed_string = mixed_string_buffer_result (&msb); tp->type = last_token_type = token_type_mtemplate; break; } + + if (IS_UNICODE (uc)) + { + assert (UNICODE_VALUE (uc) >= 0 + && UNICODE_VALUE (uc) < 0x110000); + mixed_string_buffer_append_unicode (&msb, + UNICODE_VALUE (uc)); + } + else + mixed_string_buffer_append_char (&msb, uc); } + lexical_context = lc_outside; return; } tp->type = last_token_type = token_type_rbrace; @@ -1538,6 +1696,7 @@ static void x_javascript_lex (token_ty *tp) { phase5_get (tp); + if (tp->type == token_type_string || tp->type == token_type_template) { mixed_string_ty *sum = tp->mixed_string; @@ -1573,19 +1732,40 @@ x_javascript_lex (token_ty *tp) token_ty token2; phase5_get (&token2); - if (token2.type == token_type_template) + if (token2.type == token_type_template + || token2.type == token_type_ltemplate) { - /* The value of - tag `abc` - is the value of the function call - tag (["abc"]) - We don't know anything about this value. Therefore, don't - let the extractor see this template literal. */ - free_token (&token2); + /* Merge *tp and token2: + tag `abc` becomes tag`abc` + tag `abc${ becomes tag`abc${ + */ + tp->type = token2.type; + tp->template_tag = tp->string; + tp->mixed_string = token2.mixed_string; + tp->comment = token2.comment; + tp->line_number = token2.line_number; } else phase5_unget (&token2); } + + /* Move info from the token into the current level. */ + if (tp->type == token_type_ltemplate + || tp->type == token_type_mtemplate) + { + if (!(level_type () == level_template_literal)) + abort (); + if (tp->type == token_type_ltemplate) + { + levels[level - 1].template_tag = tp->template_tag; + tp->template_tag = NULL; + levels[level - 1].template_parts = string_list_alloc (); + levels[level - 1].template_comment = tp->comment; + tp->comment = NULL; + } + string_list_append_move (levels[level - 1].template_parts, + mixed_string_contents_free1 (tp->mixed_string)); + } } @@ -1782,25 +1962,68 @@ extract_balanced (message_list_ty *mlp, case token_type_string: case token_type_template: + case token_type_rtemplate: { lex_pos_ty pos; pos.file_name = logical_file_name; pos.line_number = token.line_number; - if (extract_all) + mixed_string_ty *mixed_string = + (token.type != token_type_rtemplate ? token.mixed_string : NULL); + /* For a tagged template literal, perform the tag step 1. */ + if ((token.type == token_type_template + || token.type == token_type_rtemplate) + && token.template_tag != NULL) { - char *string = mixed_string_contents (token.mixed_string); - mixed_string_free (token.mixed_string); - remember_a_message (mlp, NULL, string, true, false, - inner_region, &pos, - NULL, token.comment, true); + const char *tag = token.template_tag; + string_list_ty *parts; + + if (token.type == token_type_template) + { + parts = string_list_alloc (); + string_list_append_move (parts, + mixed_string_contents (mixed_string)); + } + else /* (token.type == token_type_rtemplate) */ + parts = token.template_parts; + + void *tag_value; + if (tags.table != NULL + && hash_find_entry (&tags, tag, strlen (tag), &tag_value) == 0) + { + struct tag_definition *def = tag_value; + + /* Invoke the tag step 1 function. */ + char *string = def->step1_fn (parts); + + /* Extract the string. */ + remember_a_message (mlp, NULL, string, true, false, + inner_region, &pos, + NULL, token.comment, true); + } + + string_list_free (parts); + + /* Due to the tag, the value is not a constant. */ + mixed_string = NULL; + } + + if (mixed_string != NULL) + { + if (extract_all) + { + char *string = mixed_string_contents_free1 (mixed_string); + remember_a_message (mlp, NULL, string, true, false, + inner_region, &pos, + NULL, token.comment, true); + } + else + arglist_parser_remember (argparser, arg, mixed_string, + inner_region, + pos.file_name, pos.line_number, + token.comment, true); } - else - arglist_parser_remember (argparser, arg, token.mixed_string, - inner_region, - pos.file_name, pos.line_number, - token.comment, true); } drop_reference (token.comment); next_context_iter = null_context_list_iterator; @@ -1844,7 +2067,6 @@ extract_balanced (message_list_ty *mlp, case token_type_ltemplate: case token_type_mtemplate: - case token_type_rtemplate: case token_type_keyword: case token_type_start: case token_type_dot: diff --git a/gettext-tools/src/x-javascript.h b/gettext-tools/src/x-javascript.h index 09b530704..545b68711 100644 --- a/gettext-tools/src/x-javascript.h +++ b/gettext-tools/src/x-javascript.h @@ -1,5 +1,5 @@ /* xgettext JavaScript backend. - Copyright (C) 2002-2003, 2006, 2010, 2013-2014, 2018, 2020 Free Software Foundation, Inc. + Copyright (C) 2002-2024 Free Software Foundation, Inc. This file was written by Andreas Stricker , 2010. It's based on x-python from Bruno Haible. @@ -46,6 +46,8 @@ extern void x_javascript_extract_all (void); extern void init_flag_table_javascript (void); +extern void x_javascript_tag (const char *tag); + #ifdef __cplusplus } diff --git a/gettext-tools/src/xgettext.c b/gettext-tools/src/xgettext.c index fb185464f..989bbcade 100644 --- a/gettext-tools/src/xgettext.c +++ b/gettext-tools/src/xgettext.c @@ -276,6 +276,7 @@ static const struct option long_options[] = { "string-limit", required_argument, NULL, 'l' }, { "stringtable-output", no_argument, NULL, CHAR_MAX + 7 }, { "style", required_argument, NULL, CHAR_MAX + 15 }, + { "tag", required_argument, NULL, CHAR_MAX + 21 }, { "trigraphs", no_argument, NULL, 'T' }, { "verbose", no_argument, NULL, 'v' }, { "version", no_argument, NULL, 'V' }, @@ -672,12 +673,16 @@ main (int argc, char *argv[]) error (EXIT_FAILURE, 0, _("sentence end type '%s' unknown"), optarg); break; + case CHAR_MAX + 19: /* --itstool */ + add_itstool_comments = true; + break; + case CHAR_MAX + 20: /* --its */ explicit_its_filename = optarg; break; - case CHAR_MAX + 19: /* --itstool */ - add_itstool_comments = true; + case CHAR_MAX + 21: /* --tag */ + x_javascript_tag (optarg); break; default: @@ -1164,6 +1169,11 @@ Language specific options:\n")); C#, awk, YCP, Tcl, Perl, PHP, GCC-source,\n\ Lua, JavaScript, Vala)\n")); printf (_("\ + --tag=WORD:FORMAT defines the behaviour of tagged template literals\n\ + with tag WORD\n")); + printf (_("\ + (only language JavaScript)\n")); + printf (_("\ -T, --trigraphs understand ANSI C trigraphs for input\n")); printf (_("\ (deprecated; only languages C, C++, ObjectiveC)\n")); diff --git a/gettext-tools/tests/Makefile.am b/gettext-tools/tests/Makefile.am index d71b797ea..0bbd1c022 100644 --- a/gettext-tools/tests/Makefile.am +++ b/gettext-tools/tests/Makefile.am @@ -119,6 +119,7 @@ TESTS = gettext-1 gettext-2 \ xgettext-javascript-4 xgettext-javascript-5 xgettext-javascript-6 \ xgettext-javascript-7 xgettext-javascript-8 xgettext-javascript-9 \ xgettext-javascript-10 xgettext-javascript-11 xgettext-javascript-12 \ + xgettext-javascript-13 \ xgettext-javascript-stackovfl-1 xgettext-javascript-stackovfl-2 \ xgettext-javascript-stackovfl-3 xgettext-javascript-stackovfl-4 \ xgettext-javascript-stackovfl-5 xgettext-javascript-stackovfl-6 \ diff --git a/gettext-tools/tests/xgettext-javascript-13 b/gettext-tools/tests/xgettext-javascript-13 new file mode 100755 index 000000000..60b165d7e --- /dev/null +++ b/gettext-tools/tests/xgettext-javascript-13 @@ -0,0 +1,178 @@ +#!/bin/sh +. "${srcdir=.}/init.sh"; path_prepend_ . ../src + +# Test of JavaScript support: tagged template literals. + +cat <<\EOF > xg-js-13.js +var s0 = _`A template literal without substitutions`; +var s1 = _ `A template literal with +embedded +newlines`); +var s2 = _ /* XYZZY */ `A template literal with ${n} substitutions`; +var s3 = _`A template literal with several substitutions: ${a} and ${b} and ${c} and so on`; +var s4 = `/${looks_like_regex}`; +var s5 = _('not part of a regex'); +var s6 = _`that's a valid string. ` + _('This too'); +var s7 = _ tag`A template literal with two tags`; +var s8 = _`a${_`b${_`c`+d}`}e`; +var s9 = _("a normal string"); +var s10 = _`abc${foo({},_('should be extracted'))}xyz`; +var f1 = function () { + return _("first normal string") + _`${foo}` + _("second normal string"); +}; +var s11 = _("another normal string"); +var s12 = { property: _`A template literal with ${n} substitution` }; +var s13 = _("yet another normal string"); +// Mixing JSX with template literals. +var s14 = 0; +var s15 = ( +
+ _("Expected translation string #10"); + {_`pre11${ _("Expected translation string #11") }post11`} + _("Expected translation string #12"); +
+); +var s16 = _("Expected translation string #13"); +var s17 =
; +var s18 = _("Expected translation string #15"); +var s19 = { a: 1, b:
}; +var s20 = _("Expected translation string #17"); +var s21 = _`begin${
_("Expected translation string #18")
}end`; +var s22 = _("Expected translation string #19"); +EOF + +: ${XGETTEXT=xgettext} +${XGETTEXT} --add-comments --no-location --tag=_:javascript-gnome-format \ + -o xg-js-13.tmp xg-js-13.js 2>xg-js-13.err +test $? = 0 || { cat xg-js-13.err; Exit 1; } +func_filter_POT_Creation_Date xg-js-13.tmp xg-js-13.pot + +cat <<\EOF > xg-js-13.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 , 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 \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "A template literal without substitutions" +msgstr "" + +msgid "" +"A template literal with\n" +"embedded\n" +"newlines" +msgstr "" + +#. XYZZY +msgid "A template literal with {0} substitutions" +msgstr "" + +msgid "" +"A template literal with several substitutions: {0} and {1} and {2} and so on" +msgstr "" + +msgid "not part of a regex" +msgstr "" + +msgid "that's a valid string. " +msgstr "" + +msgid "This too" +msgstr "" + +msgid "c" +msgstr "" + +msgid "b{0}" +msgstr "" + +msgid "a{0}e" +msgstr "" + +msgid "a normal string" +msgstr "" + +msgid "should be extracted" +msgstr "" + +msgid "abc{0}xyz" +msgstr "" + +msgid "first normal string" +msgstr "" + +msgid "{0}" +msgstr "" + +msgid "second normal string" +msgstr "" + +msgid "another normal string" +msgstr "" + +msgid "A template literal with {0} substitution" +msgstr "" + +msgid "yet another normal string" +msgstr "" + +msgid "Expected translation string #10" +msgstr "" + +msgid "Expected translation string #11" +msgstr "" + +msgid "pre11{0}post11" +msgstr "" + +msgid "Expected translation string #12" +msgstr "" + +msgid "Expected translation string #13" +msgstr "" + +msgid "Expected translation string #14" +msgstr "" + +msgid "pre14{0}post14" +msgstr "" + +msgid "Expected translation string #15" +msgstr "" + +msgid "Expected translation string #16" +msgstr "" + +msgid "pre16{0}post16" +msgstr "" + +msgid "Expected translation string #17" +msgstr "" + +msgid "Expected translation string #18" +msgstr "" + +msgid "begin{0}end" +msgstr "" + +msgid "Expected translation string #19" +msgstr "" +EOF + +: ${DIFF=diff} +${DIFF} xg-js-13.ok xg-js-13.pot +result=$? + +exit $result