- C++:
xgettext now supports single-quotes in number tokens, as specified in
C++14.
+ - Shell:
+ o The programs 'gettext', 'ngettext' now support a --context argument.
+ o gettext.sh contains new function eval_pgettext and eval_npgettext
+ for producing translations of messages with context.
- Java:
o xgettext now supports UTF-8 encoded .properties files (a new feature
of Java 9).
@noindent @strong{Arguments}
@table @samp
+@item -c @var{context}
+@itemx --context=@var{context}
+@opindex -c@r{, @code{gettext} option}
+@opindex --context@r{, @code{gettext} option}
+Specify the context for the messages to be translated.
+See @ref{Contexts} for details.
+
@item -d @var{textdomain}
@itemx --domain=@var{textdomain}
@opindex -d@r{, @code{gettext} option}
@noindent @strong{Arguments}
@table @samp
+@item -c @var{context}
+@itemx --context=@var{context}
+@opindex -c@r{, @code{ngettext} option}
+@opindex --context@r{, @code{ngettext} option}
+Specify the context for the messages to be translated.
+See @ref{Contexts} for details.
+
@item -d @var{textdomain}
@itemx --domain=@var{textdomain}
@opindex -d@r{, @code{ngettext} option}
/* Long options. */
static const struct option long_options[] =
{
+ { "context", required_argument, NULL, 'c' },
{ "domain", required_argument, NULL, 'd' },
{ "help", no_argument, NULL, 'h' },
{ "shell-script", no_argument, NULL, 's' },
bool do_version = false;
const char *domain = getenv ("TEXTDOMAIN");
const char *domaindir = getenv ("TEXTDOMAINDIR");
+ const char *context = NULL;
add_newline = true;
do_expand = false;
atexit (close_stdout);
/* Parse command line options. */
- while ((optchar = getopt_long (argc, argv, "+d:eEhnsV", long_options, NULL))
+ while ((optchar = getopt_long (argc, argv, "+c:d:eEhnsV", long_options, NULL))
!= EOF)
switch (optchar)
{
case '\0': /* Long option. */
break;
+ case 'c':
+ context = optarg;
+ break;
case 'd':
domain = optarg;
break;
bindtextdomain (domain, domaindir);
/* Write out the result. */
- fputs (dgettext (domain, msgid), stdout);
+ fputs ((context != NULL
+ ? dpgettext_expr (domain, context, msgid)
+ : dgettext (domain, msgid)),
+ stdout);
}
}
else
msgid = expand_escape (msgid);
/* Write out the result. */
- fputs (domain == NULL ? msgid : dgettext (domain, msgid),
+ fputs ((domain == NULL ? msgid :
+ context != NULL
+ ? dpgettext_expr (domain, context, msgid)
+ : dgettext (domain, msgid)),
stdout);
/* We separate the arguments by a single ' '. */
printf ("\n");
/* xgettext: no-wrap */
printf (_("\
- -d, --domain=TEXTDOMAIN retrieve translated messages from TEXTDOMAIN\n\
- -e enable expansion of some escape sequences\n\
- -E (ignored for compatibility)\n\
- -h, --help display this help and exit\n\
- -n suppress trailing newline\n\
- -V, --version display version information and exit\n\
+ -d, --domain=TEXTDOMAIN retrieve translated messages from TEXTDOMAIN\n"));
+ printf (_("\
+ -c, --context=CONTEXT specify context for MSGID\n"));
+ printf (_("\
+ -e enable expansion of some escape sequences\n"));
+ printf (_("\
+ -n suppress trailing newline\n"));
+ printf (_("\
+ -E (ignored for compatibility)\n"));
+ printf (_("\
[TEXTDOMAIN] MSGID retrieve translated message corresponding\n\
to MSGID from TEXTDOMAIN\n"));
printf ("\n");
+ printf (_("\
+Informative output:\n"));
+ printf (_("\
+ -h, --help display this help and exit\n"));
+ printf (_("\
+ -V, --version display version information and exit\n"));
+ printf ("\n");
/* xgettext: no-wrap */
printf (_("\
If the TEXTDOMAIN parameter is not given, the domain is determined from the\n\
ngettext "$1" "$2" "$3" | (export PATH `envsubst --variables "$1 $2"`; envsubst "$1 $2")
}
+# eval_pgettext MSGCTXT MSGID
+# looks up the translation of MSGID in the context MSGCTXT and substitutes
+# shell variables in the result.
+eval_pgettext () {
+ gettext --context="$1" "$2" | (export PATH `envsubst --variables "$2"`; envsubst "$2")
+}
+
+# eval_npgettext MSGCTXT MSGID MSGID-PLURAL COUNT
+# looks up the translation of MSGID / MSGID-PLURAL for COUNT in the context
+# MSGCTXT and substitutes shell variables in the result.
+eval_npgettext () {
+ ngettext --context="$1" "$2" "$3" "$4" | (export PATH `envsubst --variables "$2 $3"`; envsubst "$2 $3")
+}
+
# Note: This use of envsubst is much safer than using the shell built-in 'eval'
# would be.
# 1) The security problem with Chinese translations that happen to use a
/* Long options. */
static const struct option long_options[] =
{
+ { "context", required_argument, NULL, 'c' },
{ "domain", required_argument, NULL, 'd' },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
bool do_version = false;
const char *domain = getenv ("TEXTDOMAIN");
const char *domaindir = getenv ("TEXTDOMAINDIR");
+ const char *context = NULL;
do_expand = false;
/* Set program name for message texts. */
atexit (close_stdout);
/* Parse command line options. */
- while ((optchar = getopt_long (argc, argv, "+d:eEhV", long_options, NULL))
+ while ((optchar = getopt_long (argc, argv, "+c:d:eEhV", long_options, NULL))
!= EOF)
switch (optchar)
{
case '\0': /* Long option. */
break;
+ case 'c':
+ context = optarg;
+ break;
case 'd':
domain = optarg;
break;
bindtextdomain (domain, domaindir);
/* Write out the result. */
- fputs (dngettext (domain, msgid, msgid_plural, n), stdout);
+ fputs ((context != NULL
+ ? dnpgettext_expr (domain, context, msgid, msgid_plural, n)
+ : dngettext (domain, msgid, msgid_plural, n)),
+ stdout);
}
exit (EXIT_SUCCESS);
printf ("\n");
/* xgettext: no-wrap */
printf (_("\
- -d, --domain=TEXTDOMAIN retrieve translated message from TEXTDOMAIN\n\
- -e enable expansion of some escape sequences\n\
- -E (ignored for compatibility)\n\
- -h, --help display this help and exit\n\
- -V, --version display version information and exit\n\
- [TEXTDOMAIN] retrieve translated message from TEXTDOMAIN\n\
- MSGID MSGID-PLURAL translate MSGID (singular) / MSGID-PLURAL (plural)\n\
+ -d, --domain=TEXTDOMAIN retrieve translated message from TEXTDOMAIN\n"));
+ printf (_("\
+ -c, --context=CONTEXT specify context for MSGID\n"));
+ printf (_("\
+ -e enable expansion of some escape sequences\n"));
+ printf (_("\
+ -E (ignored for compatibility)\n"));
+ printf (_("\
+ [TEXTDOMAIN] retrieve translated message from TEXTDOMAIN\n"));
+ printf (_("\
+ MSGID MSGID-PLURAL translate MSGID (singular) / MSGID-PLURAL (plural)\n"));
+ printf (_("\
COUNT choose singular/plural form based on this value\n"));
+ printf ("\n");
+ printf (_("\
+Informative output:\n"));
+ printf (_("\
+ -h, --help display this help and exit\n"));
+ printf (_("\
+ -V, --version display version information and exit\n"));
printf ("\n");
/* xgettext: no-wrap */
printf (_("\
* envsubst Invocation:: Invoking the @code{envsubst} program
* eval_gettext Invocation:: Invoking the @code{eval_gettext} function
* eval_ngettext Invocation:: Invoking the @code{eval_ngettext} function
+* eval_pgettext Invocation:: Invoking the @code{eval_pgettext} function
+* eval_npgettext Invocation:: Invoking the @code{eval_npgettext} function
Perl
@pindex gettext
@pindex ngettext
@code{gettext}, @code{ngettext} programs
-@*@code{eval_gettext}, @code{eval_ngettext} shell functions
+@*@code{eval_gettext}, @code{eval_ngettext}, @code{eval_pgettext},
+@code{eval_npgettext} shell functions
@item textdomain
@vindex TEXTDOMAIN@r{, environment variable}
* envsubst Invocation:: Invoking the @code{envsubst} program
* eval_gettext Invocation:: Invoking the @code{eval_gettext} function
* eval_ngettext Invocation:: Invoking the @code{eval_ngettext} function
+* eval_pgettext Invocation:: Invoking the @code{eval_pgettext} function
+* eval_npgettext Invocation:: Invoking the @code{eval_npgettext} function
@end menu
@node Preparing Shell Scripts, gettext.sh, sh, sh
near the top of the script. @code{gettext.sh} is a shell function library
that provides the functions
-@code{eval_gettext} (see @ref{eval_gettext Invocation}) and
-@code{eval_ngettext} (see @ref{eval_ngettext Invocation}).
+@code{eval_gettext} (see @ref{eval_gettext Invocation}),
+@code{eval_ngettext} (see @ref{eval_ngettext Invocation}),
+@code{eval_pgettext} (see @ref{eval_pgettext Invocation}), and
+@code{eval_npgettext} (see @ref{eval_npgettext Invocation}).
You have to ensure that @code{gettext.sh} can be found in the @code{PATH}.
@item
@item eval_ngettext
See @ref{eval_ngettext Invocation}.
+
+@item eval_pgettext
+See @ref{eval_pgettext Invocation}.
+
+@item eval_npgettext
+See @ref{eval_npgettext Invocation}.
@end itemize
@node gettext Invocation, ngettext Invocation, gettext.sh, sh
performing dollar-substitution on the result. Note that only shell variables
mentioned in @var{msgid} will be dollar-substituted in the result.
-@node eval_ngettext Invocation, , eval_gettext Invocation, sh
+@node eval_ngettext Invocation, eval_pgettext Invocation, eval_gettext Invocation, sh
@subsubsection Invoking the @code{eval_ngettext} function
@cindex @code{eval_ngettext} function, usage
on the result. Note that only shell variables mentioned in @var{msgid} or
@var{msgid-plural} will be dollar-substituted in the result.
+@node eval_pgettext Invocation, eval_npgettext Invocation, eval_ngettext Invocation, sh
+@subsubsection Invoking the @code{eval_pgettext} function
+
+@cindex @code{eval_pgettext} function, usage
+@example
+eval_pgettext @var{msgctxt} @var{msgid}
+@end example
+
+@cindex lookup message translation with context
+This function outputs the native language translation of a textual message
+in the given context @var{msgctxt} (see @ref{Contexts}), performing
+dollar-substitution on the result. Note that only shell variables mentioned
+in @var{msgid} will be dollar-substituted in the result.
+
+@node eval_npgettext Invocation, , eval_pgettext Invocation, sh
+@subsubsection Invoking the @code{eval_npgettext} function
+
+@cindex @code{eval_npgettext} function, usage
+@example
+eval_npgettext @var{msgctxt} @var{msgid} @var{msgid-plural} @var{count}
+@end example
+
+@cindex lookup plural message translation with context
+This function outputs the native language translation of a textual message
+whose grammatical form depends on a number in the given context @var{msgctxt}
+(see @ref{Contexts}), performing dollar-substitution on the result. Note
+that only shell variables mentioned in @var{msgid} or @var{msgid-plural}
+will be dollar-substituted in the result.
+
@node bash, Python, sh, List of Programming Languages
@subsection bash - Bourne-Again Shell Script
@cindex bash
@item
For Shell scripts: @code{gettext}, @code{ngettext:1,2}, @code{eval_gettext},
-@code{eval_ngettext:1,2}.
+@code{eval_ngettext:1,2}, @code{eval_pgettext:1c,2},
+@code{eval_npgettext:1c,2,3}.
@item
For Python: @code{gettext}, @code{ugettext}, @code{dgettext:2},
/* xgettext sh backend.
- Copyright (C) 2003, 2005-2009, 2015-2016 Free Software Foundation, Inc.
+ Copyright (C) 2003, 2005-2009, 2015-2016, 2018 Free Software Foundation, Inc.
Written by Bruno Haible <bruno@clisp.org>, 2003.
This program is free software: you can redistribute it and/or modify
xgettext.texi! */
x_sh_keyword ("gettext");
x_sh_keyword ("ngettext:1,2");
+ /* Note: There is also special handling for 'gettext' and 'ngettext'
+ in read_command, below. */
x_sh_keyword ("eval_gettext");
x_sh_keyword ("eval_ngettext:1,2");
+ x_sh_keyword ("eval_pgettext:1c,2");
+ x_sh_keyword ("eval_npgettext:1c,2,3");
default_keywords = false;
}
}
xgettext_record_flag ("eval_gettext:1:sh-format");
xgettext_record_flag ("eval_ngettext:1:sh-format");
xgettext_record_flag ("eval_ngettext:2:sh-format");
+ xgettext_record_flag ("eval_pgettext:2:sh-format");
+ xgettext_record_flag ("eval_npgettext:2:sh-format");
+ xgettext_record_flag ("eval_npgettext:3:sh-format");
}
return str;
}
+/* Convert a t_string token to a char*, ignoring the first OFFSET bytes. */
+static char *
+substring_of_word (const struct word *wp, size_t offset)
+{
+ char *str;
+ int n;
+
+ if (!(wp->type == t_string))
+ abort ();
+ n = wp->token->charcount;
+ if (!(offset <= n))
+ abort ();
+ str = XNMALLOC (n - offset + 1, char);
+ memcpy (str, wp->token->chars + offset, n - offset);
+ str[n - offset] = '\0';
+ return str;
+}
+
/* Whitespace recognition. */
}
else
{
+ bool matters_for_argparser = true;
+
if (argparser == NULL)
{
/* This is the function position. */
{
/* These are the argument positions. */
if (inner.type == t_string)
- arglist_parser_remember (argparser, arg,
- string_of_word (&inner),
- inner_context,
- logical_file_name,
- inner.line_number_at_start,
- savable_comment);
-
- if (arglist_parser_decidedp (argparser, arg))
{
- /* Stop looking for arguments of the last function_name. */
- /* FIXME: What about context_iter? */
- arglist_parser_done (argparser, arg);
- shapes = NULL;
- argparser = NULL;
+ bool accepts_context =
+ ((argparser->keyword_len == 7
+ && memcmp (argparser->keyword, "gettext", 7) == 0)
+ || (argparser->keyword_len == 8
+ && memcmp (argparser->keyword, "ngettext", 8) == 0));
+ if (accepts_context && argparser->next_is_msgctxt)
+ {
+ argparser->next_is_msgctxt = false;
+ arglist_parser_remember_msgctxt (argparser,
+ string_of_word (&inner),
+ inner_context,
+ logical_file_name,
+ inner.line_number_at_start);
+ matters_for_argparser = false;
+ }
+ else if (accepts_context
+ && ((inner.token->charcount == 2
+ && memcmp (inner.token->chars, "-c", 2) == 0)
+ || (inner.token->charcount == 9
+ && memcmp (inner.token->chars, "--context", 9) == 0)))
+ {
+ argparser->next_is_msgctxt = true;
+ matters_for_argparser = false;
+ }
+ else if (accepts_context
+ && (inner.token->charcount >= 10
+ && memcmp (inner.token->chars, "--context=", 10) == 0))
+ {
+ argparser->next_is_msgctxt = false;
+ arglist_parser_remember_msgctxt (argparser,
+ substring_of_word (&inner, 10),
+ inner_context,
+ logical_file_name,
+ inner.line_number_at_start);
+ matters_for_argparser = false;
+ }
+ else
+ {
+ arglist_parser_remember (argparser, arg,
+ string_of_word (&inner),
+ inner_context,
+ logical_file_name,
+ inner.line_number_at_start,
+ savable_comment);
+ }
}
+
+ if (matters_for_argparser)
+ if (arglist_parser_decidedp (argparser, arg))
+ {
+ /* Stop looking for arguments of the last function_name. */
+ /* FIXME: What about context_iter? */
+ arglist_parser_done (argparser, arg);
+ shapes = NULL;
+ argparser = NULL;
+ }
}
- arg++;
+ if (matters_for_argparser)
+ arg++;
}
free_word (&inner);
ap->mlp = mlp;
ap->keyword = NULL;
ap->keyword_len = 0;
+ ap->next_is_msgctxt = false;
ap->nalternatives = 0;
return ap;
ap->mlp = mlp;
ap->keyword = shapes->keyword;
ap->keyword_len = shapes->keyword_len;
+ ap->next_is_msgctxt = false;
ap->nalternatives = shapes->nshapes;
for (i = 0; i < shapes->nshapes; i++)
{
copy->mlp = ap->mlp;
copy->keyword = ap->keyword;
copy->keyword_len = ap->keyword_len;
+ copy->next_is_msgctxt = ap->next_is_msgctxt;
copy->nalternatives = ap->nalternatives;
for (i = 0; i < ap->nalternatives; i++)
{
free (string);
}
+
void
arglist_parser_remember (struct arglist_parser *ap,
int argnum, char *string,
comment, LET_NONE);
}
+
+void
+arglist_parser_remember_msgctxt (struct arglist_parser *ap,
+ char *string,
+ flag_context_ty context,
+ char *file_name, size_t line_number)
+{
+ bool stored_string = false;
+ size_t nalternatives = ap->nalternatives;
+ size_t i;
+
+ for (i = 0; i < nalternatives; i++)
+ {
+ struct partial_call *cp = &ap->alternative[i];
+
+ cp->msgctxt = string;
+ cp->msgctxt_escape = LET_NONE;
+ cp->msgctxt_pos.file_name = file_name;
+ cp->msgctxt_pos.line_number = line_number;
+ stored_string = true;
+ /* Mark msgctxt as done. */
+ cp->argnumc = 0;
+ }
+ /* Note: There is a memory leak here: When string was stored but is later
+ not used by arglist_parser_done, we don't free it. */
+ if (!stored_string)
+ free (string);
+}
+
+
bool
arglist_parser_decidedp (struct arglist_parser *ap, int argnum)
{
/* xgettext common functions.
- Copyright (C) 2001-2003, 2005-2006, 2008-2009, 2011, 2015-2016 Free Software
- Foundation, Inc.
+ Copyright (C) 2001-2003, 2005-2006, 2008-2009, 2011, 2015-2016, 2018 Free Software Foundation, Inc.
Written by Peter Miller <millerp@canb.auug.org.au>
and Bruno Haible <haible@clisp.cons.org>, 2001.
message_list_ty *mlp; /* list where the message shall be added */
const char *keyword; /* the keyword, not NUL terminated */
size_t keyword_len; /* the keyword's length */
+ bool next_is_msgctxt; /* true if the next argument is the msgctxt */
size_t nalternatives; /* number of partial_call alternatives */
struct partial_call alternative[1]; /* partial_call alternatives */
};
char *file_name, size_t line_number,
refcounted_string_list_ty *comment,
enum literalstring_escape_type type);
+/* Adds a string argument as msgctxt to an arglist_parser, without incrementing
+ the current argument number.
+ STRING must be malloc()ed string; its ownership is passed to the callee.
+ FILE_NAME must be allocated with indefinite extent. */
+extern void arglist_parser_remember_msgctxt (struct arglist_parser *ap,
+ char *string,
+ flag_context_ty context,
+ char *file_name, size_t line_number);
/* Tests whether an arglist_parser has is not waiting for more arguments after
argument ARGNUM. */
extern bool arglist_parser_decidedp (struct arglist_parser *ap, int argnum);
$echo "`gettext \"'Your command, please?', asked the waiter.\"`"
$echo "`eval_ngettext \"a piece of cake\" \"\\$n pieces of cake\" $n`"
+
+$echo "`gettext -c File \"Open\"`"
+
+$echo "`gettext --context File \"Close\"`"
+
+$echo "`gettext --context=File \"Save\"`"
+
+$echo "`eval_npgettext File \"\\$n file open\" \"\\$n files open\" $n`"
EOF
: ${XGETTEXT=xgettext}
msgid_plural "$n pieces of cake"
msgstr[0] ""
msgstr[1] ""
+
+msgctxt "File"
+msgid "Open"
+msgstr ""
+
+msgctxt "File"
+msgid "Close"
+msgstr ""
+
+msgctxt "File"
+msgid "Save"
+msgstr ""
+
+#, sh-format
+msgctxt "File"
+msgid "$n file open"
+msgid_plural "$n files open"
+msgstr[0] ""
+msgstr[1] ""
EOF
: ${DIFF=diff}
msgid_plural "$n pieces of cake"
msgstr[0] "un morceau de gateau"
msgstr[1] "$n morceaux de gateau"
+
+msgctxt "File"
+msgid "Open"
+msgstr "Ouvrir"
+
+msgctxt "File"
+msgid "Close"
+msgstr "Fermer"
+
+msgctxt "File"
+msgid "Save"
+msgstr "Sauvegarder"
+
+#, sh-format
+msgctxt "File"
+msgid "$n file open"
+msgid_plural "$n files open"
+msgstr[0] "$n fichier ouvert"
+msgstr[1] "$n fichiers ouverts"
EOF
: ${MSGMERGE=msgmerge}
cat <<\EOF > prog.ok
«Votre commande, s'il vous plait», dit le garçon.
2 morceaux de gateau
+Ouvrir
+Fermer
+Sauvegarder
+2 fichiers ouverts
EOF
cat <<\EOF > prog.oku
«Votre commande, s'il vous plait», dit le garçon.
2 morceaux de gateau
+Ouvrir
+Fermer
+Sauvegarder
+2 fichiers ouverts
EOF
: ${LOCALE_FR=fr_FR}