+2005-10-01 Bruno Haible <bruno@clisp.org>
+
+ Support for context dependent translations in PO files.
+ * gettext.texi (PO Files): Explain syntax of msgctxt.
+ (MO Files): Explain how contexts are stored.
+ (Contexts): New subsection.
+ (GUI program problems): Remove subsection.
+ * msgexec.texi: Mention MSGEXEC_MSGCTXT environment variable.
+ * msggrep.texi: Document option -J.
+
2005-09-25 Bruno Haible <bruno@clisp.org>
* xgettext.texi (--from-code): Python input is no longer always in
* Ambiguities:: Solving ambiguities
* Locating Catalogs:: Locating message catalog files
* Charset conversion:: How to request conversion to Unicode
+* Contexts:: Solving ambiguities in GUI programs
* Plural forms:: Additional functions for handling plurals
-* GUI program problems:: Another technique for solving ambiguities
* Optimized gettext:: Optimization of the *gettext functions
Temporary Notes for the Programmers Chapter
@end table
+@kwindex msgctxt
+@cindex context, in PO files
+It is also possible to have entries with a context specifier. They look like
+this:
+
+@example
+@var{white-space}
+# @var{translator-comments}
+#. @var{automatic-comments}
+#: @var{reference}@dots{}
+#, @var{flag}@dots{}
+msgctxt @var{context}
+msgid @var{untranslated-string}
+msgstr @var{translated-string}
+@end example
+
+The context serves to disambiguate messages with the same
+@var{untranslated-string}. It is possible to have several entries with
+the same @var{untranslated-string} in a PO file, provided that they each
+have a different @var{context}. Note that an empty @var{context} string
+and a absent @code{msgctxt} line do not mean the same thing.
+
@kwindex msgid_plural
@cindex plural forms, in PO files
A different kind of entries is used for translations which involve
msgstr[1] "s'han trobat %d errors fatals"
@end example
+Here also, a @code{msgctxt} context can be specified before @code{msgid},
+like above.
+
It happens that some lines, usually whitespace or comments, follow the
very last entry of a PO file. Such lines are not part of any entry,
and will be dropped when the PO file is processed by the tools, or may
an offset which is a multiple of the alignment value. On some RISC
machines, a correct alignment will speed things up.
+@cindex context, in MO files
+Contexts are stored by storing the concatenation of the context, a
+@key{EOT} byte, and the original string, instead of the original string.
+
@cindex plural forms, in MO files
Plural forms are stored by letting the plural of the original string
follow the singular of the original string, separated through a
* Ambiguities:: Solving ambiguities
* Locating Catalogs:: Locating message catalog files
* Charset conversion:: How to request conversion to Unicode
+* Contexts:: Solving ambiguities in GUI programs
* Plural forms:: Additional functions for handling plurals
-* GUI program problems:: Another technique for solving ambiguities
* Optimized gettext:: Optimization of the *gettext functions
@end menu
variables.}
@code{dcgettext} specifies the locale category by the third argument.
-@node Charset conversion, Plural forms, Locating Catalogs, gettext
+@node Charset conversion, Contexts, Locating Catalogs, gettext
@subsection How to specify the output character set @code{gettext} uses
@cindex charset conversion at runtime
@cindex encoding conversion at runtime
global variable @var{errno} is set accordingly.
@end deftypefun
-@node Plural forms, GUI program problems, Charset conversion, gettext
+@node Contexts, Plural forms, Charset conversion, gettext
+@subsection Using contexts for solving ambiguities
+@cindex context
+@cindex GUI programs
+@cindex translating menu entries
+@cindex menu entries
+
+One place where the @code{gettext} functions, if used normally, have big
+problems is within programs with graphical user interfaces (GUIs). The
+problem is that many of the strings which have to be translated are very
+short. They have to appear in pull-down menus which restricts the
+length. But strings which are not containing entire sentences or at
+least large fragments of a sentence may appear in more than one
+situation in the program but might have different translations. This is
+especially true for the one-word strings which are frequently used in
+GUI programs.
+
+As a consequence many people say that the @code{gettext} approach is
+wrong and instead @code{catgets} should be used which indeed does not
+have this problem. But there is a very simple and powerful method to
+handle this kind of problems with the @code{gettext} functions.
+
+Contexts can be added to strings to be translated. A context dependent
+translation lookup is when a translation for a given string is searched,
+that is limited to a given context. The translation for the same string
+in a different context can be different. The different translations of
+the same string in different contexts can be stored in the in the same
+MO file, and can be edited by the translator in the same PO file.
+
+The @file{gettext.h} include file contains the lookup macros for strings
+with contexts. They are implemented as thin macros and inline functions
+over the functions from @code{<libintl.h>}.
+
+@findex pgettext
+@example
+const char *pgettext (const char *msgctxt, const char *msgid);
+@end example
+
+In a call of this macro, @var{msgctxt} and @var{msgid} must be string
+literals. The macro returns the translation of @var{msgid}, restricted
+to the context given by @var{msgctxt}.
+
+The @var{msgctxt} string is visible in the PO file to the translator.
+You should try to make it somehow canonical and never changing. Because
+every time you change an @var{msgctxt}, the translator will have to review
+the translation of @var{msgid}.
+
+Finding a canonical @var{msgctxt} string that doesn't change over time can
+be hard. But you shouldn't use the file name or class name containing the
+@code{pgettext} call -- because it is a common development task to rename
+a file or a class, and it shouldn't cause translator work. Also you shouldn't
+use a comment in the form of a complete English sentence as @var{msgctxt} --
+because orthography or grammar changes are often applied to such sentences,
+and again, it shouldn't force the translator to do a review.
+
+The @samp{p} in @samp{pgettext} stands for ``particular'': @code{pgettext}
+fetches a particular translation of the @var{msgid}.
+
+@findex dpgettext
+@findex dcpgettext
+@example
+const char *dpgettext (const char *domain_name,
+ const char *msgctxt, const char *msgid);
+const char *dcpgettext (const char *domain_name,
+ const char *msgctxt, const char *msgid,
+ int category);
+@end example
+
+These are generalizations of @code{pgettext}. The behave similarly to
+@code{dgettext} and @code{dcgettext}, respectively. The @var{domain_name}
+argument defines the translation domain. The @var{category} argument
+allows to use another locale facet than @code{LC_MESSAGES}.
+
+As as example consider the following fictional situation. A GUI program
+has a menu bar with the following entries:
+
+@smallexample
++------------+------------+--------------------------------------+
+| File | Printer | |
++------------+------------+--------------------------------------+
+| Open | | Select |
+| New | | Open |
++----------+ | Connect |
+ +----------+
+@end smallexample
+
+To have the strings @code{File}, @code{Printer}, @code{Open},
+@code{New}, @code{Select}, and @code{Connect} translated there has to be
+at some point in the code a call to a function of the @code{gettext}
+family. But in two places the string passed into the function would be
+@code{Open}. The translations might not be the same and therefore we
+are in the dilemma described above.
+
+What distinguishes the two places is the menu path from the menu root to
+the particular menu entries:
+
+@smallexample
+Menu|File
+Menu|Printer
+Menu|File|Open
+Menu|File|New
+Menu|Printer|Select
+Menu|Printer|Open
+Menu|Printer|Connect
+@end smallexample
+
+The context is thus the menu path without its last part. So, the calls
+look like this:
+
+@smallexample
+pgettext ("Menu|", "File")
+pgettext ("Menu|", "Printer")
+pgettext ("Menu|File|", "Open")
+pgettext ("Menu|File|", "New")
+pgettext ("Menu|Printer|", "Select")
+pgettext ("Menu|Printer|", "Open")
+pgettext ("Menu|Printer|", "Connect")
+@end smallexample
+
+Whether or not to use the @samp{|} character at the end of the context is a
+matter of style.
+
+@node Plural forms, Optimized gettext, Contexts, gettext
@subsection Additional functions for plural forms
@cindex plural forms
@end table
@end table
-@node GUI program problems, Optimized gettext, Plural forms, gettext
-@subsection How to use @code{gettext} in GUI programs
-@cindex GUI programs
-@cindex translating menu entries
-@cindex menu entries
-
-One place where the @code{gettext} functions, if used normally, have big
-problems is within programs with graphical user interfaces (GUIs). The
-problem is that many of the strings which have to be translated are very
-short. They have to appear in pull-down menus which restricts the
-length. But strings which are not containing entire sentences or at
-least large fragments of a sentence may appear in more than one
-situation in the program but might have different translations. This is
-especially true for the one-word strings which are frequently used in
-GUI programs.
-
-As a consequence many people say that the @code{gettext} approach is
-wrong and instead @code{catgets} should be used which indeed does not
-have this problem. But there is a very simple and powerful method to
-handle these kind of problems with the @code{gettext} functions.
-
-@noindent
-As as example consider the following fictional situation. A GUI program
-has a menu bar with the following entries:
-
-@smallexample
-+------------+------------+--------------------------------------+
-| File | Printer | |
-+------------+------------+--------------------------------------+
-| Open | | Select |
-| New | | Open |
-+----------+ | Connect |
- +----------+
-@end smallexample
-
-To have the strings @code{File}, @code{Printer}, @code{Open},
-@code{New}, @code{Select}, and @code{Connect} translated there has to be
-at some point in the code a call to a function of the @code{gettext}
-family. But in two places the string passed into the function would be
-@code{Open}. The translations might not be the same and therefore we
-are in the dilemma described above.
-
-One solution to this problem is to artificially enlengthen the strings
-to make them unambiguous. But what would the program do if no
-translation is available? The enlengthened string is not what should be
-printed. So we should use a little bit modified version of the functions.
-
-To enlengthen the strings a uniform method should be used. E.g., in the
-example above the strings could be chosen as
-
-@smallexample
-Menu|File
-Menu|Printer
-Menu|File|Open
-Menu|File|New
-Menu|Printer|Select
-Menu|Printer|Open
-Menu|Printer|Connect
-@end smallexample
-
-Now all the strings are different and if now instead of @code{gettext}
-the following little wrapper function is used, everything works just
-fine:
-
-@cindex sgettext
-@smallexample
- char *
- sgettext (const char *msgid)
- @{
- char *msgval = gettext (msgid);
- if (msgval == msgid)
- msgval = strrchr (msgid, '|') + 1;
- return msgval;
- @}
-@end smallexample
-
-What this little function does is to recognize the case when no
-translation is available. This can be done very efficiently by a
-pointer comparison since the return value is the input value. If there
-is no translation we know that the input string is in the format we used
-for the Menu entries and therefore contains a @code{|} character. We
-simply search for the last occurrence of this character and return a
-pointer to the character following it. That's it!
-
-If one now consistently uses the enlengthened string form and replaces
-the @code{gettext} calls with calls to @code{sgettext} (this is normally
-limited to very few places in the GUI implementation) then it is
-possible to produce a program which can be internationalized.
-
-The other @code{gettext} functions (@code{dgettext}, @code{dcgettext}
-and the @code{ngettext} equivalents) can and should have corresponding
-functions as well which look almost identical, except for the parameters
-and the call to the underlying function.
-
-Now there is of course the question why such functions do not exist in
-the GNU gettext package? There are two parts of the answer to this question.
-
-@itemize @bullet
-@item
-They are easy to write and therefore can be provided by the project they
-are used in. This is not an answer by itself and must be seen together
-with the second part which is:
-
-@item
-There is no way the gettext package can contain a version which can work
-everywhere. The problem is the selection of the character to separate
-the prefix from the actual string in the enlenghtened string. The
-examples above used @code{|} which is a quite good choice because it
-resembles a notation frequently used in this context and it also is a
-character not often used in message strings.
-
-But what if the character is used in message strings? Or if the chose
-character is not available in the character set on the machine one
-compiles (e.g., @code{|} is not required to exist for @w{ISO C}; this is
-why the @file{iso646.h} file exists in @w{ISO C} programming environments).
-@end itemize
-
-There is only one more comment to be said. The wrapper function above
-requires that the translations strings are not enlengthened themselves.
-This is only logical. There is no need to disambiguate the strings
-(since they are never used as keys for a search) and one also saves
-quite some memory and disk space by doing this.
-
-@node Optimized gettext, , GUI program problems, gettext
+@node Optimized gettext, , Plural forms, gettext
@subsection Optimization of the *gettext functions
@cindex optimization of @code{gettext} functions
by a null byte. The output of @samp{msgexec 0} is suitable as input for
@samp{xargs -0}.
+@vindex MSGEXEC_MSGCTXT@r{, environment variable}
@vindex MSGEXEC_MSGID@r{, environment variable}
@vindex MSGEXEC_LOCATION@r{, environment variable}
During each @var{command} invocation, the environment variable
@code{MSGEXEC_MSGID} is bound to the message's msgid, and the environment
variable @code{MSGEXEC_LOCATION} is bound to the location in the PO file
-of the message.
+of the message. If the message has a context, the environment variable
+@code{MSGEXEC_MSGCTXT} is bound to the message's msgctxt, otherwise it is
+unbound.
@cindex catalog encoding and @code{msgexec} output
Note: It is your responsibility to ensure that the @var{command} can cope
@example
[-N @var{sourcefile}]... [-M @var{domainname}]...
- [-K @var{msgid-pattern}] [-T @var{msgstr-pattern}] [-C @var{comment-pattern}]
+ [-J @var{msgctxt-pattern} [-K @var{msgid-pattern}] [-T @var{msgstr-pattern}]
+ [-C @var{comment-pattern}]
@end example
A message is selected if
@itemize @bullet
@item it comes from one of the specified source files,
@item or if it comes from one of the specified domains,
+@item or if @samp{-J} is given and its context (msgctxt) matches
+ @var{msgctxt-pattern},
@item or if @samp{-K} is given and its key (msgid or msgid_plural) matches
@var{msgid-pattern},
@item or if @samp{-T} is given and its translation (msgstr) matches
@opindex --domain@r{, @code{msggrep} option}
Select messages belonging to domain @var{domainname}.
+@item -J
+@itemx --msgctxt
+@opindex -J@r{, @code{msggrep} option}
+@opindex --msgctxt@r{, @code{msggrep} option}
+Start of patterns for the msgctxt.
+
@item -K
@itemx --msgid
@opindex -K@r{, @code{msggrep} option}
+2005-10-01 Bruno Haible <bruno@clisp.org>
+
+ Support for context dependent translations in PO files.
+ * gettext.h (GETTEXT_CONTEXT_GLUE): New macro.
+ (pgettext, dpgettext, dcpgettext, npgettext, dnpgettext, dcnpgettext):
+ New macros.
+ (pgettext_aux, npgettext_aux): New inline functions.
+
2005-08-23 Bruno Haible <bruno@clisp.org>
* byteswap_.h: New file, from gnulib.
/* Convenience header for conditional use of GNU <libintl.h>.
- Copyright (C) 1995-1998, 2000-2002, 2004 Free Software Foundation, Inc.
+ Copyright (C) 1995-1998, 2000-2002, 2004-2005 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published
initializer for static 'char[]' or 'const char[]' variables. */
#define gettext_noop(String) String
+/* The separator between msgctxt and msgid in a .mo file. */
+#define GETTEXT_CONTEXT_GLUE "\004"
+
+/* Pseudo function calls, taking a MSGCTXT and a MSGID instead of just a
+ MSGID. MSGCTXT and MSGID must be string literals. MSGCTXT should be
+ short and rarely need to change.
+ The letter 'p' stands for 'particular' or 'special'. */
+#define pgettext(Msgctxt, Msgid) \
+ pgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES)
+#define dpgettext(Domainname, Msgctxt, Msgid) \
+ pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES)
+#define dcpgettext(Domainname, Msgctxt, Msgid, Category) \
+ pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, Category)
+#define npgettext(Msgctxt, Msgid, MsgidPlural, N) \
+ npgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES)
+#define dnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N) \
+ npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES)
+#define dcnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N, Category) \
+ npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, Category)
+
+#ifdef __GNUC__
+__inline
+#else
+#ifdef __cplusplus
+inline
+#endif
+#endif
+static const char *
+pgettext_aux (const char *domain,
+ const char *msg_ctxt_id, const char *msgid,
+ int category)
+{
+ const char *translation = dcgettext (domain, msg_ctxt_id, category);
+ if (translation == msg_ctxt_id)
+ return msgid;
+ else
+ return translation;
+}
+
+#ifdef __GNUC__
+__inline
+#else
+#ifdef __cplusplus
+inline
+#endif
+#endif
+static const char *
+npgettext_aux (const char *domain,
+ const char *msg_ctxt_id, const char *msgid,
+ const char *msgid_plural, unsigned long int n,
+ int category)
+{
+ const char *translation =
+ dcngettext (domain, msg_ctxt_id, msgid_plural, n, category);
+ if (translation == msg_ctxt_id || translation == msgid_plural)
+ return (n == 1 ? msgid : msgid_plural);
+ else
+ return translation;
+}
+
#endif /* _LIBGETTEXT_H */
+2005-10-01 Bruno Haible <bruno@clisp.org>
+
+ Support for context dependent translations in PO files.
+ * message.h (MSGCTXT_SEPARATOR): New macro.
+ (struct message_ty): Add 'msgctxt' field.
+ (message_alloc): Add msgctxt argument.
+ (is_header): New macro.
+ (message_list_search, message_list_search_fuzzy,
+ message_list_list_search, message_list_list_search_fuzzy,
+ msgdomain_list_search, msgdomain_list_search_fuzzy): Add msgctxt
+ argument.
+ * message.c: Include xallocsa.h.
+ (message_alloc): Add msgctxt argument.
+ (message_copy): Update.
+ (message_list_hash_insert_entry): New function.
+ (message_list_append, message_list_prepend, message_list_insert_at,
+ message_list_msgids_changed): Use it.
+ (message_list_search): Add msgctxt argument.
+ (message_list_search_fuzzy_inner): Likewise.
+ (message_list_search_fuzzy): Likewise.
+ (message_list_list_search): Likewise.
+ (message_list_list_search_fuzzy): Likewise.
+ * msgl-ascii.c (is_ascii_message): Also test the msgctxt.
+ * write-po.c (message_print): Warn if some msgctxt has non-ASCII
+ characters. Write out the msgctxt.
+ (message_print_obsolete): Likewise.
+ (msgdomain_list_print_po): Use is_header macro.
+ (msgdomain_list_print): Likewise. Bail out if contexts are present and
+ cannot be stored in the given output format.
+ * write-properties.c (write_message): Use is_header macro.
+ * po-lex.c: Include message.h.
+ (keyword_p): Also recognize 'msgctxt'.
+ (po_gram_lex): Bail out if a string contains the EOT character.
+ * read-po-abstract.h (struct abstract_po_reader_class_ty): Add msgctxt
+ argument to directive_message function pointer.
+ (po_callback_message): Add msgctxt argument.
+ * read-po-abstract.c (call_directive_message, po_callback_message): Add
+ msgctxt argument.
+ * po-gram-gen.y (do_callback_message): Add msgctxt argument. Use
+ is_header macro.
+ (MSGCTXT): New token type.
+ (message_intro, MSGCTXT): Declare return types.
+ (message): Use message_intro instead of just MSGID.
+ (message_intro): New nonterminal reduction rules.
+ * read-properties.c (properties_parse): Update.
+ * read-stringtable.c (stringtable_parse): Update.
+ * read-po.h (struct default_po_reader_class_ty): Add msgctxt argument
+ to add_message field.
+ (default_directive_message, default_add_message): Likewise.
+ * read-po.c (call_add_message, default_directive_message,
+ default_add_message): Add msgctxt argument.
+ * msgl-iconv.c (convert_msgid): Also convert the msgctxt.
+ (iconv_message_list): Use is_header macro. Test also the msgctxt for
+ non-ASCII-ness.
+ * msgl-cat.c (is_message_selected, is_message_needed): Use is_header
+ macro.
+ (catenate_msgdomain_list): Likewise. Update.
+ * msgl-equal.c (message_equal): Likewise. Also compare the msgctxt.
+ * msgcmp.c (is_message_selected): Use is_header macro.
+ (match_domain): Update.
+ (compare): Use is_header macro.
+ * msgmerge.c (message_merge, match_domain): Use is_header macro.
+ Update.
+ (merge): Update.
+ * msgattrib.c (is_message_selected): Use is_header macro.
+ (process_message_list): Likewise. Update.
+ * msgl-charset.c (compare_po_locale_charsets): Use is_header macro.
+ * msgexec.c (process_string): Also set or unset MSGEXEC_MSGCTXT
+ variable.
+ * msgfilter.c (process_message): Use is_header macro.
+ * msggrep.c (grep_task): Increase size from 3 to 4.
+ (long_options): Add --msgctxt option.
+ (main): Accept -J/--msgctxt option. Update grep_pass numbers.
+ (no_pass): Update.
+ (usage): Mention -J option.
+ (is_message_selected): Use is_header macro. Perform a new grep pass on
+ the msgctxt.
+ * msginit.c (fill_header): Use is_header macro. Update.
+ (update_msgstr_plurals): Update.
+ * read-mo.c (read_mo_file): Split msgid into msgctxt and msgid. Update.
+ * read-tcl.c (msgdomain_read_tcl): Use is_header macro.
+ * msgl-check.c (check_plural): Update.
+ (check_message): Use is_header macro.
+ * write-mo.c (write_table): Write msgid with msgctxt, instead of just
+ the msgid.
+ * write-java.c: Include xerror.h.
+ (write_java_code): Update.
+ (msgdomain_write_java): Give error if some entries have a context.
+ * write-csharp.c: Include xerror.h.
+ (write_csharp_code): Update.
+ (msgdomain_write_csharp): Give error if some entries have a context.
+ * write-resources.c (msgdomain_write_csharp_resources): Bail out if
+ contexts are present and cannot be stored in the given output format.
+ * write-tcl.c (write_msg): Use is_header macro.
+ (msgdomain_write_tcl): Bail out if contexts are present and cannot be
+ stored in the given output format.
+ * write-qt.c: Include hash.h.
+ (write_qm): Use is_header macro. Write out msgctxt if present. Write
+ a contexts section if appropriate.
+ * msgfmt.c (msgfmt_add_message): Add msgctxt argument.
+ (msgfmt_frob_new_message): Use is_header macro.
+ * xgettext.c (exclude_directive_message): Add msgctxt argument.
+ (remember_a_message, construct_header, finalize_header): Update.
+ * x-po.c (extract_add_message): Add msgctxt argument. Update.
+ (extract): Update.
+ * gettext-po.h (po_message_msgctxt, po_message_set_msgctxt): New
+ declarations.
+ * gettext-po.c (po_file_domain_header): Use is_header macro.
+ (po_message_create): Update.
+ (po_message_msgctxt, po_message_set_msgctxt): New functions.
+ (po_message_check_all): Use is_header macro.
+
2005-10-01 Bruno Haible <bruno@clisp.org>
Avoid a crash when msgcat or msgconv is asked to convert a non-ASCII
mlp = msgdomain_list_sublist (file->mdlp, domain, false);
if (mlp != NULL)
for (j = 0; j < mlp->nitems; j++)
- if (mlp->item[j]->msgid[0] == '\0' && !mlp->item[j]->obsolete)
+ if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
{
const char *header = mlp->item[j]->msgstr;
{
lex_pos_ty pos = { NULL, 0 };
- return (po_message_t) message_alloc (NULL, NULL, NULL, 0, &pos);
+ return (po_message_t) message_alloc (NULL, NULL, NULL, NULL, 0, &pos);
+}
+
+
+/* Return the context of a message, or NULL for a message not restricted to a
+ context. */
+const char *
+po_message_msgctxt (po_message_t message)
+{
+ message_ty *mp = (message_ty *) message;
+
+ return mp->msgctxt;
+}
+
+
+/* Change the context of a message. NULL means a message not restricted to a
+ context. */
+void
+po_message_set_msgctxt (po_message_t message, const char *msgctxt)
+{
+ message_ty *mp = (message_ty *) message;
+
+ if (msgctxt != mp->msgctxt)
+ {
+ char *old_msgctxt = (char *) mp->msgctxt;
+
+ mp->msgctxt = (msgctxt != NULL ? xstrdup (msgctxt) : NULL);
+ if (old_msgctxt != NULL)
+ free (old_msgctxt);
+ }
}
msgdomain_list_sublist (iterator->file->mdlp, iterator->domain, false);
if (mlp != NULL)
for (j = 0; j < mlp->nitems; j++)
- if (mlp->item[j]->msgid[0] == '\0' && !mlp->item[j]->obsolete)
+ if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
{
header = mlp->item[j];
break;
To finish initializing the message, you must set the msgid and msgstr. */
extern po_message_t po_message_create (void);
+/* Return the context of a message, or NULL for a message not restricted to a
+ context. */
+extern const char * po_message_msgctxt (po_message_t message);
+
+/* Change the context of a message. NULL means a message not restricted to a
+ context. */
+extern void po_message_set_msgctxt (po_message_t message, const char *msgctxt);
+
/* Return the msgid (untranslated English string) of a message. */
extern const char * po_message_msgid (po_message_t message);
/* GNU gettext - internationalization aids
- Copyright (C) 1995-1998, 2000-2004 Free Software Foundation, Inc.
+ Copyright (C) 1995-1998, 2000-2005 Free Software Foundation, Inc.
This file was written by Peter Miller <millerp@canb.auug.org.au>
#include "fstrcmp.h"
#include "hash.h"
#include "xalloc.h"
+#include "xallocsa.h"
const char *const format_language[NFORMATS] =
message_ty *
-message_alloc (const char *msgid, const char *msgid_plural,
+message_alloc (const char *msgctxt,
+ const char *msgid, const char *msgid_plural,
const char *msgstr, size_t msgstr_len,
const lex_pos_ty *pp)
{
size_t i;
mp = (message_ty *) xmalloc (sizeof (message_ty));
+ mp->msgctxt = msgctxt;
mp->msgid = msgid;
mp->msgid_plural = (msgid_plural != NULL ? xstrdup (msgid_plural) : NULL);
mp->msgstr = msgstr;
message_ty *result;
size_t j, i;
- result = message_alloc (xstrdup (mp->msgid), mp->msgid_plural,
+ result = message_alloc (mp->msgctxt != NULL ? xstrdup (mp->msgctxt) : NULL,
+ xstrdup (mp->msgid), mp->msgid_plural,
mp->msgstr, mp->msgstr_len, &mp->pos);
if (mp->comment)
}
+static int
+message_list_hash_insert_entry (hash_table *htable, message_ty *mp)
+{
+ char *alloced_key;
+ const char *key;
+ size_t keylen;
+ int found;
+
+ if (mp->msgctxt != NULL)
+ {
+ /* Concatenate mp->msgctxt and mp->msgid, to form the hash table key. */
+ size_t msgctxt_len = strlen (mp->msgctxt);
+ size_t msgid_len = strlen (mp->msgid);
+ keylen = msgctxt_len + 1 + msgid_len + 1;
+ alloced_key = (char *) xallocsa (keylen);
+ memcpy (alloced_key, mp->msgctxt, msgctxt_len);
+ alloced_key[msgctxt_len] = MSGCTXT_SEPARATOR;
+ memcpy (alloced_key + msgctxt_len + 1, mp->msgid, msgid_len + 1);
+ key = alloced_key;
+ }
+ else
+ {
+ alloced_key = NULL;
+ key = mp->msgid;
+ keylen = strlen (mp->msgid) + 1;
+ }
+
+ found = insert_entry (htable, key, keylen, mp);
+
+ if (mp->msgctxt != NULL)
+ freesa (alloced_key);
+
+ return found;
+}
+
+
void
message_list_append (message_list_ty *mlp, message_ty *mp)
{
mlp->item[mlp->nitems++] = mp;
if (mlp->use_hashtable)
- if (insert_entry (&mlp->htable, mp->msgid, strlen (mp->msgid) + 1, mp))
+ if (message_list_hash_insert_entry (&mlp->htable, mp))
/* A message list has duplicates, although it was allocated with the
assertion that it wouldn't have duplicates. It is a bug. */
abort ();
mlp->nitems++;
if (mlp->use_hashtable)
- if (insert_entry (&mlp->htable, mp->msgid, strlen (mp->msgid) + 1, mp))
+ if (message_list_hash_insert_entry (&mlp->htable, mp))
/* A message list has duplicates, although it was allocated with the
assertion that it wouldn't have duplicates. It is a bug. */
abort ();
mlp->nitems++;
if (mlp->use_hashtable)
- if (insert_entry (&mlp->htable, mp->msgid, strlen (mp->msgid) + 1, mp))
+ if (message_list_hash_insert_entry (&mlp->htable, mp))
/* A message list has duplicates, although it was allocated with the
assertion that it wouldn't have duplicates. It is a bug. */
abort ();
{
message_ty *mp = mlp->item[j];
- if (insert_entry (&mlp->htable, mp->msgid, strlen (mp->msgid) + 1,
- mp))
+ if (message_list_hash_insert_entry (&mlp->htable, mp))
/* A message list has duplicates, although it was allocated with
the assertion that it wouldn't have duplicates, and before the
msgids changed it indeed didn't have duplicates. */
message_ty *
-message_list_search (message_list_ty *mlp, const char *msgid)
+message_list_search (message_list_ty *mlp,
+ const char *msgctxt, const char *msgid)
{
if (mlp->use_hashtable)
{
- void *htable_value;
+ char *alloced_key;
+ const char *key;
+ size_t keylen;
- if (find_entry (&mlp->htable, msgid, strlen (msgid) + 1, &htable_value))
- return NULL;
+ if (msgctxt != NULL)
+ {
+ /* Concatenate the msgctxt and msgid, to form the hash table key. */
+ size_t msgctxt_len = strlen (msgctxt);
+ size_t msgid_len = strlen (msgid);
+ keylen = msgctxt_len + 1 + msgid_len + 1;
+ alloced_key = (char *) xallocsa (keylen);
+ memcpy (alloced_key, msgctxt, msgctxt_len);
+ alloced_key[msgctxt_len] = MSGCTXT_SEPARATOR;
+ memcpy (alloced_key + msgctxt_len + 1, msgid, msgid_len + 1);
+ key = alloced_key;
+ }
else
- return (message_ty *) htable_value;
+ {
+ alloced_key = NULL;
+ key = msgid;
+ keylen = strlen (msgid) + 1;
+ }
+
+ {
+ void *htable_value;
+ int found = !find_entry (&mlp->htable, key, keylen, &htable_value);
+
+ if (msgctxt != NULL)
+ freesa (alloced_key);
+
+ if (found)
+ return (message_ty *) htable_value;
+ else
+ return NULL;
+ }
}
else
{
message_ty *mp;
mp = mlp->item[j];
- if (strcmp (msgid, mp->msgid) == 0)
+ if ((msgctxt != NULL
+ ? mp->msgctxt != NULL && strcmp (msgctxt, mp->msgctxt) == 0
+ : mp->msgctxt == NULL)
+ && strcmp (msgid, mp->msgid) == 0)
return mp;
}
return NULL;
static message_ty *
-message_list_search_fuzzy_inner (message_list_ty *mlp, const char *msgid,
+message_list_search_fuzzy_inner (message_list_ty *mlp,
+ const char *msgctxt, const char *msgid,
double *best_weight_p)
{
size_t j;
if (mp->msgstr != NULL && mp->msgstr[0] != '\0')
{
- double weight = fstrcmp (msgid, mp->msgid);
+ double weight =
+ fstrcmp (msgid, mp->msgid)
+ /* A translation for a context is a good proposal also for
+ another. But give mp a small advantage if mp is valid
+ regardless of any context or has the same context as the
+ one being looked up. */
+ + ((mp->msgctxt == NULL
+ || (msgctxt != NULL && strcmp (msgctxt, mp->msgctxt) == 0))
+ ? 0.00001
+ : 0);
if (weight > *best_weight_p)
{
*best_weight_p = weight;
message_ty *
-message_list_search_fuzzy (message_list_ty *mlp, const char *msgid)
+message_list_search_fuzzy (message_list_ty *mlp,
+ const char *msgctxt, const char *msgid)
{
double best_weight;
best_weight = 0.6;
- return message_list_search_fuzzy_inner (mlp, msgid, &best_weight);
+ return message_list_search_fuzzy_inner (mlp, msgctxt, msgid, &best_weight);
}
message_ty *
-message_list_list_search (message_list_list_ty *mllp, const char *msgid)
+message_list_list_search (message_list_list_ty *mllp,
+ const char *msgctxt, const char *msgid)
{
message_ty *best_mp;
int best_weight; /* 0: not found, 1: found without msgstr, 2: translated */
message_ty *mp;
mlp = mllp->item[j];
- mp = message_list_search (mlp, msgid);
+ mp = message_list_search (mlp, msgctxt, msgid);
if (mp)
{
int weight = (mp->msgstr_len == 1 && mp->msgstr[0] == '\0' ? 1 : 2);
message_ty *
-message_list_list_search_fuzzy (message_list_list_ty *mllp, const char *msgid)
+message_list_list_search_fuzzy (message_list_list_ty *mllp,
+ const char *msgctxt, const char *msgid)
{
size_t j;
double best_weight;
message_ty *mp;
mlp = mllp->item[j];
- mp = message_list_search_fuzzy_inner (mlp, msgid, &best_weight);
+ mp = message_list_search_fuzzy_inner (mlp, msgctxt, msgid, &best_weight);
if (mp)
best_mp = mp;
}
#if 0 /* unused */
message_ty *
-msgdomain_list_search (msgdomain_list_ty *mdlp, const char *msgid)
+msgdomain_list_search (msgdomain_list_ty *mdlp,
+ const char *msgctxt, const char *msgid)
{
size_t j;
message_ty *mp;
mdp = mdlp->item[j];
- mp = message_list_search (mdp->messages, msgid);
+ mp = message_list_search (mdp->messages, msgctxt, msgid);
if (mp)
return mp;
}
#if 0 /* unused */
message_ty *
-msgdomain_list_search_fuzzy (msgdomain_list_ty *mdlp, const char *msgid)
+msgdomain_list_search_fuzzy (msgdomain_list_ty *mdlp,
+ const char *msgctxt, const char *msgid)
{
size_t j;
double best_weight;
message_ty *mp;
mdp = mdlp->item[j];
- mp = message_list_search_fuzzy_inner (mdp->messages, msgid, &best_weight);
+ mp = message_list_search_fuzzy_inner (mdp->messages, msgctxt, msgid,
+ &best_weight);
if (mp)
best_mp = mp;
}
/* GNU gettext - internationalization aids
- Copyright (C) 1995-1998, 2000-2004 Free Software Foundation, Inc.
+ Copyright (C) 1995-1998, 2000-2005 Free Software Foundation, Inc.
This file was written by Peter Miller <millerp@canb.auug.org.au>
#define MESSAGE_DOMAIN_DEFAULT "messages"
+/* Separator between msgctxt and msgid in .mo files. */
+#define MSGCTXT_SEPARATOR '\004' /* EOT */
+
+
/* Kinds of format strings. */
enum format_type
{
typedef struct message_ty message_ty;
struct message_ty
{
+ /* The msgctxt string, if present. */
+ const char *msgctxt;
+
/* The msgid string. */
const char *msgid;
};
extern message_ty *
- message_alloc (const char *msgid, const char *msgid_plural,
+ message_alloc (const char *msgctxt,
+ const char *msgid, const char *msgid_plural,
const char *msgstr, size_t msgstr_len,
const lex_pos_ty *pp);
+#define is_header(mp) ((mp)->msgctxt == NULL && (mp)->msgid[0] == '\0')
extern void
message_free (message_ty *mp);
extern void
extern void
message_list_remove_if_not (message_list_ty *mlp,
message_predicate_ty *predicate);
-/* Recompute the hash table of a message list after the msgids changed. */
+/* Recompute the hash table of a message list after the msgids or msgctxts
+ changed. */
extern bool
message_list_msgids_changed (message_list_ty *mlp);
extern message_ty *
- message_list_search (message_list_ty *mlp, const char *msgid);
+ message_list_search (message_list_ty *mlp,
+ const char *msgctxt, const char *msgid);
extern message_ty *
- message_list_search_fuzzy (message_list_ty *mlp, const char *msgid);
+ message_list_search_fuzzy (message_list_ty *mlp,
+ const char *msgctxt, const char *msgid);
typedef struct message_list_list_ty message_list_list_ty;
message_list_list_ty *mllp2);
extern message_ty *
message_list_list_search (message_list_list_ty *mllp,
- const char *msgid);
+ const char *msgctxt, const char *msgid);
extern message_ty *
message_list_list_search_fuzzy (message_list_list_ty *mllp,
- const char *msgid);
+ const char *msgctxt, const char *msgid);
typedef struct msgdomain_ty msgdomain_ty;
msgdomain_list_sublist (msgdomain_list_ty *mdlp, const char *domain,
bool create);
extern message_ty *
- msgdomain_list_search (msgdomain_list_ty *mdlp, const char *msgid);
+ msgdomain_list_search (msgdomain_list_ty *mdlp,
+ const char *msgctxt, const char *msgid);
extern message_ty *
- msgdomain_list_search_fuzzy (msgdomain_list_ty *mdlp, const char *msgid);
+ msgdomain_list_search_fuzzy (msgdomain_list_ty *mdlp,
+ const char *msgctxt, const char *msgid);
#ifdef __cplusplus
is_message_selected (const message_ty *mp)
{
/* Always keep the header entry. */
- if (mp->msgid[0] == '\0')
+ if (is_header (mp))
return true;
if ((to_remove & (REMOVE_UNTRANSLATED | REMOVE_TRANSLATED))
/* Attribute changes only affect messages listed in --only-file
and not listed in --ignore-file. */
if ((only_mlp
- ? message_list_search (only_mlp, mp->msgid) != NULL
+ ? message_list_search (only_mlp, mp->msgctxt, mp->msgid) != NULL
: true)
&& (ignore_mlp
- ? message_list_search (ignore_mlp, mp->msgid) == NULL
+ ? message_list_search (ignore_mlp, mp->msgctxt, mp->msgid) == NULL
: true))
{
if (to_change & SET_FUZZY)
if (to_change & RESET_FUZZY)
mp->is_fuzzy = false;
/* Always keep the header entry non-obsolete. */
- if ((to_change & SET_OBSOLETE) && (mp->msgid[0] != '\0'))
+ if ((to_change & SET_OBSOLETE) && !is_header (mp))
mp->obsolete = true;
if (to_change & RESET_OBSOLETE)
mp->obsolete = false;
is_message_selected (const message_ty *mp)
{
/* Always keep the header entry. */
- if (mp->msgid[0] == '\0')
+ if (is_header (mp))
return true;
return !mp->obsolete;
refmsg = refmlp->item[j];
/* See if it is in the other file. */
- defmsg = message_list_search (defmlp, refmsg->msgid);
+ defmsg = message_list_search (defmlp, refmsg->msgctxt, refmsg->msgid);
if (defmsg)
defmsg->used = 1;
else
similar message, it could be a typo, or the suggestion may
help. */
(*nerrors)++;
- defmsg = message_list_search_fuzzy (defmlp, refmsg->msgid);
+ defmsg =
+ message_list_search_fuzzy (defmlp, refmsg->msgctxt, refmsg->msgid);
if (defmsg)
{
po_gram_error_at_line (&refmsg->pos, _("\
message_list_ty *mlp = ref->item[k]->messages;
for (j = 0; j < mlp->nitems; j++)
- if (mlp->item[j]->msgid[0] == '\0' /* && !mlp->item[j]->obsolete */)
+ if (is_header (mlp->item[j]) /* && !mlp->item[j]->obsolete */)
{
const char *header = mlp->item[j]->msgstr;
int exitstatus;
/* Set environment variables for the subprocess. */
+ if (mp->msgctxt != NULL)
+ xsetenv ("MSGEXEC_MSGCTXT", mp->msgctxt, 1);
+ else
+ unsetenv ("MSGEXEC_MSGCTXT");
xsetenv ("MSGEXEC_MSGID", mp->msgid, 1);
location = xasprintf ("%s:%ld", mp->pos.file_name,
(long) mp->pos.line_number);
size_t k;
/* Keep the header entry unmodified, if --keep-header was given. */
- if (mp->msgid[0] == '\0' && keep_header)
+ if (is_header (mp) && keep_header)
return;
/* Count NUL delimited substrings. */
static void
msgfmt_add_message (default_po_reader_ty *this,
+ char *msgctxt,
char *msgid,
lex_pos_ty *msgid_pos,
char *msgid_plural,
}
/* Invoke superclass method. */
- default_add_message (this, msgid, msgid_pos, msgid_plural,
+ default_add_message (this, msgctxt, msgid, msgid_pos, msgid_plural,
msgstr, msgstr_len, msgstr_pos, force_fuzzy, obsolete);
}
Also don't emit fuzzy entries, unless --use-fuzzy was specified.
But ignore fuzziness of the header entry. */
if ((!include_untranslated && mp->msgstr[0] == '\0')
- || (!include_fuzzies && mp->is_fuzzy && mp->msgid[0] != '\0'))
+ || (!include_fuzzies && mp->is_fuzzy && !is_header (mp)))
{
if (check_compatibility)
{
else
{
/* Test for header entry. */
- if (mp->msgid[0] == '\0')
+ if (is_header (mp))
{
this->has_header_entry = true;
if (!mp->is_fuzzy)
bool case_insensitive;
void *compiled_patterns;
};
-static struct grep_task grep_task[3];
+static struct grep_task grep_task[4];
/* Long options. */
static const struct option long_options[] =
{ "ignore-case", no_argument, NULL, 'i' },
{ "indent", no_argument, NULL, CHAR_MAX + 2 },
{ "location", required_argument, NULL, 'N' },
+ { "msgctxt", no_argument, NULL, 'J' },
{ "msgid", no_argument, NULL, 'K' },
{ "msgstr", no_argument, NULL, 'T' },
{ "no-escape", no_argument, NULL, CHAR_MAX + 3 },
location_files = string_list_alloc ();
domain_names = string_list_alloc ();
- for (i = 0; i < 3; i++)
+ for (i = 0; i < 4; i++)
{
struct grep_task *gt = &grep_task[i];
gt->case_insensitive = false;
}
- while ((opt = getopt_long (argc, argv, "CD:e:Ef:FhiKM:N:o:pPTVw:",
+ while ((opt = getopt_long (argc, argv, "CD:e:Ef:FhiJKM:N:o:pPTVw:",
long_options, NULL))
!= EOF)
switch (opt)
break;
case 'C':
- grep_pass = 2;
+ grep_pass = 3;
break;
case 'D':
grep_task[grep_pass].case_insensitive = true;
break;
- case 'K':
+ case 'J':
grep_pass = 0;
break;
+ case 'K':
+ grep_pass = 1;
+ break;
+
case 'M':
string_list_append (domain_names, optarg);
break;
break;
case 'T':
- grep_pass = 1;
+ grep_pass = 2;
break;
case 'V':
"--sort-output", "--sort-by-file");
/* Compile the patterns. */
- for (grep_pass = 0; grep_pass < 3; grep_pass++)
+ for (grep_pass = 0; grep_pass < 4; grep_pass++)
{
struct grep_task *gt = &grep_task[grep_pass];
if (grep_task[0].pattern_count > 0
|| grep_task[1].pattern_count > 0
- || grep_task[2].pattern_count > 0)
+ || grep_task[2].pattern_count > 0
+ || grep_task[3].pattern_count > 0)
{
/* Warn if the current locale is not suitable for this PO file. */
compare_po_locale_charsets (result);
no_pass (int opt)
{
error (EXIT_SUCCESS, 0,
- _("option '%c' cannot be used before 'K' or 'T' or 'C' has been specified"),
+ _("option '%c' cannot be used before 'J' or 'K' or 'T' or 'C' has been specified"),
opt);
usage (EXIT_FAILURE);
}
printf (_("\
Message selection:\n\
[-N SOURCEFILE]... [-M DOMAINNAME]...\n\
- [-K MSGID-PATTERN] [-T MSGSTR-PATTERN] [-C COMMENT-PATTERN]\n\
+ [-J MSGCTXT-PATTERN] [-K MSGID-PATTERN] [-T MSGSTR-PATTERN]\n\
+ [-C COMMENT-PATTERN]\n\
A message is selected if it comes from one of the specified source files,\n\
or if it comes from one of the specified domains,\n\
+or if -J is given and its context (msgctxt) matches MSGCTXT-PATTERN,\n\
or if -K is given and its key (msgid or msgid_plural) matches MSGID-PATTERN,\n\
or if -T is given and its translation (msgstr) matches MSGSTR-PATTERN,\n\
or if -C is given and the translator's comment matches COMMENT-PATTERN.\n\
When more than one selection criterion is specified, the set of selected\n\
messages is the union of the selected messages of each criterion.\n\
\n\
-MSGID-PATTERN or MSGSTR-PATTERN or COMMENT-PATTERN syntax:\n\
+MSGCTXT-PATTERN or MSGID-PATTERN or MSGSTR-PATTERN or COMMENT-PATTERN syntax:\n\
[-E | -F] [-e PATTERN | -f FILE]...\n\
PATTERNs are basic regular expressions by default, or extended regular\n\
expressions if -E is given, or fixed strings if -F is given.\n\
\n\
-N, --location=SOURCEFILE select messages extracted from SOURCEFILE\n\
-M, --domain=DOMAINNAME select messages belonging to domain DOMAINNAME\n\
+ -J, --msgctxt start of patterns for the msgctxt\n\
-K, --msgid start of patterns for the msgid\n\
-T, --msgstr start of patterns for the msgstr\n\
-C, --comment start of patterns for the translator's comment\n\
const char *p;
/* Always keep the header entry. */
- if (mp->msgid[0] == '\0')
+ if (is_header (mp))
return true;
/* Test whether one of mp->filepos[] is selected. */
if (filename_list_match (location_files, mp->filepos[i].file_name))
return true;
+ /* Test msgctxt using the --msgctxt arguments. */
+ if (mp->msgctxt != NULL
+ && is_string_selected (0, mp->msgctxt, strlen (mp->msgctxt)))
+ return true;
+
/* Test msgid and msgid_plural using the --msgid arguments. */
- if (is_string_selected (0, mp->msgid, strlen (mp->msgid)))
+ if (is_string_selected (1, mp->msgid, strlen (mp->msgid)))
return true;
if (mp->msgid_plural != NULL
- && is_string_selected (0, mp->msgid_plural, strlen (mp->msgid_plural)))
+ && is_string_selected (1, mp->msgid_plural, strlen (mp->msgid_plural)))
return true;
/* Test msgstr using the --msgstr arguments. */
{
size_t length = strlen (p);
- if (is_string_selected (1, p, length))
+ if (is_string_selected (2, p, length))
return true;
p += length + 1;
}
/* Test translator comments using the --comment arguments. */
- if (grep_task[2].pattern_count > 0
+ if (grep_task[3].pattern_count > 0
&& mp->comment != NULL && mp->comment->nitems > 0)
{
size_t length;
if (q != total_comment + length)
abort ();
- selected = is_string_selected (2, total_comment, length);
+ selected = is_string_selected (3, total_comment, length);
freesa (total_comment);
/* Search the header entry. */
for (j = 0; j < mlp->nitems; j++)
- if (mlp->item[j]->msgid[0] == '\0' && !mlp->item[j]->obsolete)
+ if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
{
header_mp = mlp->item[j];
break;
{
static lex_pos_ty pos = { __FILE__, __LINE__ };
- header_mp = message_alloc ("", NULL, "", 1, &pos);
+ header_mp = message_alloc (NULL, "", NULL, "", 1, &pos);
message_list_prepend (mlp, header_mp);
}
char *untranslated_plural_msgstr;
size_t j;
- header_entry = message_list_search (mlp, "");
+ header_entry = message_list_search (mlp, NULL, "");
nplurals = get_plural_count (header_entry ? header_entry->msgstr : NULL);
untranslated_plural_msgstr = (char *) xmalloc (nplurals);
memset (untranslated_plural_msgstr, '\0', nplurals);
if (mp->msgid_plural != NULL && !is_ascii_string (mp->msgid_plural))
return false;
+ /* Likewise for msgctxt. */
+ if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt))
+ return false;
+
return true;
}
{
int used = (tmp->used >= 0 ? tmp->used : - tmp->used);
- return (tmp->msgid[0] == '\0'
+ return (is_header (tmp)
? !omit_header /* keep the header entry */
: (used > more_than && used < less_than));
}
is_message_needed (const message_ty *mp)
{
if (!msgcomm_mode
- && ((mp->msgid[0] != '\0' && mp->is_fuzzy) || mp->msgstr[0] == '\0'))
+ && ((!is_header (mp) && mp->is_fuzzy) || mp->msgstr[0] == '\0'))
/* Weak translation. Needed if there are only weak translations. */
return mp->tmp->used < 0 && is_message_selected (mp->tmp);
else
if (mlp->nitems > 0)
{
for (j = 0; j < mlp->nitems; j++)
- if (mlp->item[j]->msgid[0] == '\0' && !mlp->item[j]->obsolete)
+ if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
{
const char *header = mlp->item[j]->msgstr;
char *project_id = NULL;
for (j = 0; j < mlp->nitems; j++)
- if (mlp->item[j]->msgid[0] == '\0' && !mlp->item[j]->obsolete)
+ if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
{
const char *header = mlp->item[j]->msgstr;
message_ty *tmp;
size_t i;
- tmp = message_list_search (total_mlp, mp->msgid);
+ tmp = message_list_search (total_mlp, mp->msgctxt, mp->msgid);
if (tmp == NULL)
{
- tmp = message_alloc (mp->msgid, mp->msgid_plural, NULL, 0,
- &mp->pos);
+ tmp = message_alloc (mp->msgctxt, mp->msgid, mp->msgid_plural,
+ NULL, 0, &mp->pos);
tmp->is_fuzzy = true; /* may be set to false later */
for (i = 0; i < NFORMATS; i++)
tmp->is_format[i] = undecided; /* may be set to yes/no later */
}
if (!msgcomm_mode
- && ((mp->msgid[0] != '\0' && mp->is_fuzzy)
+ && ((!is_header (mp) && mp->is_fuzzy)
|| mp->msgstr[0] == '\0'))
/* Weak translation. Counted as negative tmp->used. */
{
multiline_error (xstrdup (""),
xasprintf (_("\
Conversion of file %s from %s encoding to %s encoding\n\
-changes some msgids.\n\
-Either change all msgids to be pure ASCII, or ensure they are\n\
+changes some msgids or msgctxts.\n\
+Either change all msgids and msgctxts to be pure ASCII, or ensure they are\n\
UTF-8 encoded from the beginning, i.e. already in your source code files.\n"),
files[n], canon_charsets[n][k],
canon_to_code));
/* Message list charset and locale charset handling.
- Copyright (C) 2001-2003 Free Software Foundation, Inc.
+ Copyright (C) 2001-2003, 2005 Free Software Foundation, Inc.
Written by Bruno Haible <haible@clisp.cons.org>, 2001.
This program is free software; you can redistribute it and/or modify
const message_list_ty *mlp = mdlp->item[k]->messages;
for (j = 0; j < mlp->nitems; j++)
- if (mlp->item[j]->msgid[0] == '\0' && !mlp->item[j]->obsolete)
+ if (mlp->item[j]->msgstr == NULL
+ && mlp->item[j]->msgid[0] == '\0' && !mlp->item[j]->obsolete)
{
const char *header = mlp->item[j]->msgstr;
/* Look at the plural entry for this domain.
Cf, function extract_plural_expression. */
- header = message_list_search (mlp, "");
+ header = message_list_search (mlp, NULL, "");
if (header != NULL)
{
const char *nullentry;
int check_compatibility,
int check_accelerators, char accelerator_char)
{
- if (check_header && mp->msgid[0] == '\0')
+ if (check_header && is_header (mp))
check_header_entry (mp, mp->msgstr);
return check_pair (mp,
/* Message list test for equality.
- Copyright (C) 2001-2002 Free Software Foundation, Inc.
+ Copyright (C) 2001-2002, 2005 Free Software Foundation, Inc.
Written by Bruno Haible <haible@clisp.cons.org>, 2001.
This program is free software; you can redistribute it and/or modify
{
size_t i, i1, i2;
+ if (!(mp1->msgctxt != NULL
+ ? mp2->msgctxt != NULL && strcmp (mp1->msgctxt, mp2->msgctxt) == 0
+ : mp2->msgctxt == NULL))
+ return false;
+
if (strcmp (mp1->msgid, mp2->msgid) != 0)
return false;
: mp2->msgid_plural == NULL))
return false;
- if (mp1->msgid[0] == '\0' && ignore_potcdate
+ if (is_header (mp1) && ignore_potcdate
? !msgstr_equal_ignoring_potcdate (mp1->msgstr, mp1->msgstr_len,
mp2->msgstr, mp2->msgstr_len)
: !msgstr_equal (mp1->msgstr, mp1->msgstr_len,
convert_msgid (iconv_t cd, message_ty *mp,
const struct conversion_context* context)
{
+ if (mp->msgctxt != NULL)
+ mp->msgctxt = convert_string (cd, mp->msgctxt, context);
mp->msgid = convert_string (cd, mp->msgid, context);
if (mp->msgid_plural != NULL)
mp->msgid_plural = convert_string (cd, mp->msgid_plural, context);
/* Search the header entry, and extract and replace the charset name. */
for (j = 0; j < mlp->nitems; j++)
- if (mlp->item[j]->msgid[0] == '\0' && !mlp->item[j]->obsolete)
+ if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
{
const char *header = mlp->item[j]->msgstr;
{
message_ty *mp = mlp->item[j];
- if (!is_ascii_string (mp->msgid))
+ if ((mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt))
+ || !is_ascii_string (mp->msgid))
msgids_changed = true;
context.message = mp;
convert_string_list (cd, mp->comment, &context);
CANON_TO_CODE. The (already canonicalized) encoding before conversion
can be passed as CANON_FROM_CODE; if NULL is passed instead, the
encoding is looked up in the header entry. Returns true if and only if
- some msgid changed due to the conversion. */
+ some msgctxt or msgid changed due to the conversion. */
extern bool
iconv_message_list (message_list_ty *mlp,
const char *canon_from_code,
is usually empty, as it was generated by xgettext. If we currently
process the header entry we have to merge the msgstr by using the
Report-Msgid-Bugs-To and POT-Creation-Date fields from the reference. */
- if (ref->msgid[0] == '\0')
+ if (is_header (ref))
{
/* Oh, oh. The header entry and we have something to fill in. */
static const struct
msgstr_len = def->msgstr_len;
}
- result = message_alloc (xstrdup (ref->msgid), ref->msgid_plural,
+ result = message_alloc (ref->msgctxt != NULL ? xstrdup (ref->msgctxt) : NULL,
+ xstrdup (ref->msgid), ref->msgid_plural,
msgstr, msgstr_len, &def->pos);
/* Take the comments from the definition file. There will be none at
char *untranslated_plural_msgstr;
size_t j;
- header_entry = message_list_search (definitions->item[0], "");
+ header_entry = message_list_search (definitions->item[0], NULL, "");
nplurals = get_plural_count (header_entry ? header_entry->msgstr : NULL);
untranslated_plural_msgstr = (char *) xmalloc (nplurals);
memset (untranslated_plural_msgstr, '\0', nplurals);
refmsg = refmlp->item[j];
/* See if it is in the other file. */
- defmsg = message_list_list_search (definitions, refmsg->msgid);
+ defmsg =
+ message_list_list_search (definitions, refmsg->msgctxt, refmsg->msgid);
if (defmsg)
{
/* Merge the reference with the definition: take the #. and
defmsg->used = 1;
stats->merged++;
}
- else if (refmsg->msgid[0] != '\0')
+ else if (!is_header (refmsg))
{
/* If the message was not defined at all, try to find a very
similar message, it could be a typo, or the suggestion may
if (use_fuzzy_matching
&& ((defmsg =
message_list_list_search_fuzzy (definitions,
+ refmsg->msgctxt,
refmsg->msgid)) != NULL))
{
message_ty *mp;
if (problematic & 1)
{
/* Need to know nplurals of the result domain. */
- message_ty *header_entry = message_list_search (resultmlp, "");
+ message_ty *header_entry =
+ message_list_search (resultmlp, NULL, "");
nplurals = get_plural_count (header_entry
? header_entry->msgstr
ref = read_po_file (fn2);
/* Add a dummy header entry, if the references file contains none. */
for (k = 0; k < ref->nitems; k++)
- if (message_list_search (ref->item[k]->messages, "") == NULL)
+ if (message_list_search (ref->item[k]->messages, NULL, "") == NULL)
{
static lex_pos_ty pos = { __FILE__, __LINE__ };
- message_ty *refheader = message_alloc ("", NULL, "", 1, &pos);
+ message_ty *refheader = message_alloc (NULL, "", NULL, "", 1, &pos);
message_list_prepend (ref->item[k]->messages, refheader);
}
message_list_ty *mlp = ref->item[k]->messages;
for (j = 0; j < mlp->nitems; j++)
- if (mlp->item[j]->msgid[0] == '\0' && !mlp->item[j]->obsolete)
+ if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
{
const char *header = mlp->item[j]->msgstr;
/* GNU gettext - internationalization aids
- Copyright (C) 1995-1996, 1998, 2000-2001, 2003 Free Software Foundation, Inc.
+ Copyright (C) 1995-1996, 1998, 2000-2001, 2003, 2005 Free Software Foundation, Inc.
This file was written by Peter Miller <pmiller@agso.gov.au>
po_gram_error_at_line (&(value2).pos, _("inconsistent use of #~"));
static inline void
-do_callback_message (char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural,
+do_callback_message (char *msgctxt,
+ char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural,
char *msgstr, size_t msgstr_len, lex_pos_ty *msgstr_pos,
bool obsolete)
{
/* Test for header entry. Ignore fuzziness of the header entry. */
- if (msgid[0] == '\0' && !obsolete)
+ if (msgctxt == NULL && msgid[0] == '\0' && !obsolete)
po_lex_charset_set (msgstr, gram_pos.file_name);
- po_callback_message (msgid, msgid_pos, msgid_plural,
+ po_callback_message (msgctxt,
+ msgid, msgid_pos, msgid_plural,
msgstr, msgstr_len, msgstr_pos,
false, obsolete);
}
%token COMMENT
%token DOMAIN
%token JUNK
+%token MSGCTXT
%token MSGID
%token MSGID_PLURAL
%token MSGSTR
struct { struct msgstr_def rhs; lex_pos_ty pos; bool obsolete; } rhs;
}
-%type <string> STRING COMMENT NAME msgid_pluralform
+%type <string> STRING COMMENT NAME message_intro msgid_pluralform
%type <stringlist> string_list
%type <number> NUMBER
-%type <pos> DOMAIN MSGID MSGID_PLURAL MSGSTR '[' ']'
+%type <pos> DOMAIN MSGCTXT MSGID MSGID_PLURAL MSGSTR '[' ']'
%type <rhs> pluralform pluralform_list
%right MSGSTR
;
message
- : MSGID string_list MSGSTR string_list
+ : message_intro string_list MSGSTR string_list
{
char *string2 = string_list_concat_destroy (&$2.stringlist);
char *string4 = string_list_concat_destroy (&$4.stringlist);
check_obsolete ($1, $3);
check_obsolete ($1, $4);
if (!$1.obsolete || pass_obsolete_entries)
- do_callback_message (string2, &$1.pos, NULL,
+ do_callback_message ($1.string, string2, &$1.pos, NULL,
string4, strlen (string4) + 1, &$3.pos,
$1.obsolete);
else
free (string4);
}
}
- | MSGID string_list msgid_pluralform pluralform_list
+ | message_intro string_list msgid_pluralform pluralform_list
{
char *string2 = string_list_concat_destroy (&$2.stringlist);
check_obsolete ($1, $3);
check_obsolete ($1, $4);
if (!$1.obsolete || pass_obsolete_entries)
- do_callback_message (string2, &$1.pos, $3.string,
+ do_callback_message ($1.string, string2, &$1.pos, $3.string,
$4.rhs.msgstr, $4.rhs.msgstr_len, &$4.pos,
$1.obsolete);
else
free ($4.rhs.msgstr);
}
}
- | MSGID string_list msgid_pluralform
+ | message_intro string_list msgid_pluralform
{
check_obsolete ($1, $2);
check_obsolete ($1, $3);
string_list_destroy (&$2.stringlist);
free ($3.string);
}
- | MSGID string_list pluralform_list
+ | message_intro string_list pluralform_list
{
check_obsolete ($1, $2);
check_obsolete ($1, $3);
string_list_destroy (&$2.stringlist);
free ($3.rhs.msgstr);
}
- | MSGID string_list
+ | message_intro string_list
{
check_obsolete ($1, $2);
po_gram_error_at_line (&$1.pos, _("missing `msgstr' section"));
}
;
+message_intro
+ : MSGID
+ {
+ $$.string = NULL;
+ $$.pos = $1.pos;
+ $$.obsolete = $1.obsolete;
+ }
+ | MSGCTXT string_list MSGID
+ {
+ check_obsolete ($1, $2);
+ check_obsolete ($1, $3);
+ $$.string = string_list_concat_destroy (&$2.stringlist);
+ $$.pos = $3.pos;
+ $$.obsolete = $3.obsolete;
+ }
+ ;
+
msgid_pluralform
: MSGID_PLURAL string_list
{
#include "po-error.h"
#include "po-xerror.h"
#include "pos.h"
+#include "message.h"
#include "str-list.h"
#include "po-gram-gen2.h"
return MSGID_PLURAL;
if (!strcmp (s, "msgstr"))
return MSGSTR;
+ if (!strcmp (s, "msgctxt"))
+ return MSGCTXT;
po_gram_error_at_line (&gram_pos, _("keyword \"%s\" unknown"), s);
return NAME;
}
}
buf[bufpos] = '\0';
+ /* Strings cannot contain the msgctxt separator, because it cannot
+ be faithfully represented in the msgid of a .mo file. */
+ if (strchr (buf, MSGCTXT_SEPARATOR) != NULL)
+ po_gram_error_at_line (&gram_pos,
+ _("context separator <EOT> within string"));
+
/* FIXME: Treatment of embedded \000 chars is incorrect. */
po_gram_lval.string.string = xstrdup (buf);
po_gram_lval.string.pos = gram_pos;
/* Reading binary .mo files.
- Copyright (C) 1995-1998, 2000-2004 Free Software Foundation, Inc.
+ Copyright (C) 1995-1998, 2000-2005 Free Software Foundation, Inc.
Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, April 1995.
This program is free software; you can redistribute it and/or modify
for (i = 0; i < header.nstrings; i++)
{
message_ty *mp;
+ char *msgctxt;
char *msgid;
size_t msgid_len;
+ char *separator;
char *msgstr;
size_t msgstr_len;
- /* Read the msgid. */
+ /* Read the msgctxt and msgid. */
msgid = get_string (&bf, header.orig_tab_offset + i * 8,
&msgid_len);
+ /* Split into msgctxt and msgid. */
+ separator = strchr (msgid, MSGCTXT_SEPARATOR);
+ if (separator != NULL)
+ {
+ /* The part before the MSGCTXT_SEPARATOR is the msgctxt. */
+ *separator = '\0';
+ msgctxt = msgid;
+ msgid = separator + 1;
+ msgid_len -= msgid - msgctxt;
+ }
+ else
+ msgctxt = NULL;
/* Read the msgstr. */
msgstr = get_string (&bf, header.trans_tab_offset + i * 8,
&msgstr_len);
- mp = message_alloc (msgid,
+ mp = message_alloc (msgctxt,
+ msgid,
(strlen (msgid) + 1 < msgid_len
? msgid + strlen (msgid) + 1
: NULL),
for (i = 0; i < header.n_sysdep_strings; i++)
{
message_ty *mp;
+ char *msgctxt;
char *msgid;
size_t msgid_len;
+ char *separator;
char *msgstr;
size_t msgstr_len;
nls_uint32 offset;
- /* Read the msgid. */
+ /* Read the msgctxt and msgid. */
offset = get_uint32 (&bf, header.orig_sysdep_tab_offset + i * 4);
msgid = get_sysdep_string (&bf, offset, &header, &msgid_len);
+ /* Split into msgctxt and msgid. */
+ separator = strchr (msgid, MSGCTXT_SEPARATOR);
+ if (separator != NULL)
+ {
+ /* The part before the MSGCTXT_SEPARATOR is the msgctxt. */
+ *separator = '\0';
+ msgctxt = msgid;
+ msgid = separator + 1;
+ msgid_len -= msgid - msgctxt;
+ }
+ else
+ msgctxt = NULL;
/* Read the msgstr. */
offset = get_uint32 (&bf, header.trans_sysdep_tab_offset + i * 4);
msgstr = get_sysdep_string (&bf, offset, &header, &msgstr_len);
- mp = message_alloc (msgid,
+ mp = message_alloc (msgctxt,
+ msgid,
(strlen (msgid) + 1 < msgid_len
? msgid + strlen (msgid) + 1
: NULL),
static inline void
call_directive_message (abstract_po_reader_ty *pop,
+ char *msgctxt,
char *msgid,
lex_pos_ty *msgid_pos,
char *msgid_plural,
bool force_fuzzy, bool obsolete)
{
if (pop->methods->directive_message)
- pop->methods->directive_message (pop, msgid, msgid_pos, msgid_plural,
+ pop->methods->directive_message (pop, msgctxt,
+ msgid, msgid_pos, msgid_plural,
msgstr, msgstr_len, msgstr_pos,
force_fuzzy, obsolete);
}
/* This function is called by po_gram_lex() whenever a message has been
seen. */
void
-po_callback_message (char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural,
+po_callback_message (char *msgctxt,
+ char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural,
char *msgstr, size_t msgstr_len, lex_pos_ty *msgstr_pos,
bool force_fuzzy, bool obsolete)
{
/* assert(callback_arg); */
- call_directive_message (callback_arg, msgid, msgid_pos, msgid_plural,
+ call_directive_message (callback_arg, msgctxt,
+ msgid, msgid_pos, msgid_plural,
msgstr, msgstr_len, msgstr_pos,
force_fuzzy, obsolete);
}
/* what to do with a message directive */
void (*directive_message) (struct abstract_po_reader_ty *pop,
+ char *msgctxt,
char *msgid, lex_pos_ty *msgid_pos,
char *msgid_plural,
char *msgstr, size_t msgstr_len,
/* Callbacks used by po-gram.y or po-lex.c, indirectly from po_scan. */
extern void po_callback_domain (char *name);
-extern void po_callback_message (char *msgid, lex_pos_ty *msgid_pos,
+extern void po_callback_message (char *msgctxt,
+ char *msgid, lex_pos_ty *msgid_pos,
char *msgid_plural,
char *msgstr, size_t msgstr_len,
lex_pos_ty *msgstr_pos,
static inline void
call_add_message (struct default_po_reader_ty *this,
+ char *msgctxt,
char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural,
char *msgstr, size_t msgstr_len, lex_pos_ty *msgstr_pos,
bool force_fuzzy, bool obsolete)
(default_po_reader_class_ty *) this->methods;
if (methods->add_message)
- methods->add_message (this, msgid, msgid_pos, msgid_plural,
+ methods->add_message (this, msgctxt,
+ msgid, msgid_pos, msgid_plural,
msgstr, msgstr_len, msgstr_pos,
force_fuzzy, obsolete);
}
}
-/* Process 'msgid'/'msgstr' pair from .po file. */
+/* Process ['msgctxt'/]'msgid'/'msgstr' pair from .po file. */
void
default_directive_message (abstract_po_reader_ty *that,
+ char *msgctxt,
char *msgid,
lex_pos_ty *msgid_pos,
char *msgid_plural,
{
default_po_reader_ty *this = (default_po_reader_ty *) that;
- call_add_message (this, msgid, msgid_pos, msgid_plural,
+ call_add_message (this, msgctxt, msgid, msgid_pos, msgid_plural,
msgstr, msgstr_len, msgstr_pos, force_fuzzy, obsolete);
/* Prepare for next message. */
void
default_add_message (default_po_reader_ty *this,
+ char *msgctxt,
char *msgid,
lex_pos_ty *msgid_pos,
char *msgid_plural,
mp = NULL;
else
/* See if this message ID has been seen before. */
- mp = message_list_search (this->mlp, msgid);
+ mp = message_list_search (this->mlp, msgctxt, msgid);
if (mp)
{
(allocated in po-gram-gen.y). */
free (msgstr);
free (msgid);
+ if (msgctxt != NULL)
+ free (msgctxt);
/* Add the accumulated comments to the message. */
default_copy_comment_state (this, mp);
Obsolete message go into the list at least for duplicate checking.
It's the caller's responsibility to ignore obsolete messages when
appropriate. */
- mp = message_alloc (msgid, msgid_plural, msgstr, msgstr_len, msgstr_pos);
+ mp = message_alloc (msgctxt, msgid, msgid_plural, msgstr, msgstr_len,
+ msgstr_pos);
mp->obsolete = obsolete;
default_copy_comment_state (this, mp);
if (force_fuzzy)
/* Reading PO files.
- Copyright (C) 1995-1998, 2000-2003 Free Software Foundation, Inc.
+ Copyright (C) 1995-1998, 2000-2003, 2005 Free Software Foundation, Inc.
This file was written by Bruno Haible <haible@clisp.cons.org>.
This program is free software; you can redistribute it and/or modify
/* How to add a message to the list. */
void (*add_message) (struct default_po_reader_ty *pop,
+ char *msgctxt,
char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural,
char *msgstr, size_t msgstr_len, lex_pos_ty *msgstr_pos,
bool force_fuzzy, bool obsolete);
extern void default_parse_debrief (abstract_po_reader_ty *that);
extern void default_directive_domain (abstract_po_reader_ty *that, char *name);
extern void default_directive_message (abstract_po_reader_ty *that,
+ char *msgctxt,
char *msgid,
lex_pos_ty *msgid_pos,
char *msgid_plural,
const char *s);
extern void default_set_domain (default_po_reader_ty *this, char *name);
extern void default_add_message (default_po_reader_ty *this,
+ char *msgctxt,
char *msgid,
lex_pos_ty *msgid_pos,
char *msgid_plural,
and if it is not already header/fuzzy/untranslated. */
force_fuzzy = (hidden && msgid[0] != '\0' && msgstr[0] != '\0');
- po_callback_message (msgid, &msgid_pos, NULL,
+ po_callback_message (NULL, msgid, &msgid_pos, NULL,
msgstr, strlen (msgstr) + 1, &msgstr_pos,
force_fuzzy, false);
}
necessarily designate an untranslated entry. */
msgstr = "";
msgstr_pos = msgid_pos;
- po_callback_message (msgid, &msgid_pos, NULL,
+ po_callback_message (NULL, msgid, &msgid_pos, NULL,
msgstr, strlen (msgstr) + 1, &msgstr_pos,
false, next_is_obsolete);
}
msgstr = fuzzy_msgstr;
/* A key/value pair. */
- po_callback_message (msgid, &msgid_pos, NULL,
+ po_callback_message (NULL, msgid, &msgid_pos, NULL,
msgstr, strlen (msgstr) + 1, &msgstr_pos,
false, next_is_obsolete);
}
/* Reading tcl/msgcat .msg files.
- Copyright (C) 2002-2003 Free Software Foundation, Inc.
+ Copyright (C) 2002-2003, 2005 Free Software Foundation, Inc.
Written by Bruno Haible <bruno@clisp.org>, 2002.
This program is free software; you can redistribute it and/or modify
size_t j;
for (j = 0; j < mlp->nitems; j++)
- if (mlp->item[j]->msgid[0] == '\0')
+ if (is_header (mlp->item[j]))
{
/* Found the header entry. */
if (j > 0)
#endif
#include "c-ctype.h"
-#include "error.h"
#include "relocatable.h"
+#include "error.h"
+#include "xerror.h"
#include "csharpcomp.h"
#include "message.h"
#include "mkdtemp.h"
struct expression *plural;
unsigned long int nplurals;
- header_entry = message_list_search (mlp, "");
+ header_entry = message_list_search (mlp, NULL, "");
extract_plural_expression (header_entry ? header_entry->msgstr : NULL,
&plural, &nplurals);
if (mlp->nitems == 0)
return 0;
+ /* Determine whether mlp has entries with context. */
+ {
+ bool has_context;
+ size_t j;
+
+ has_context = false;
+ for (j = 0; j < mlp->nitems; j++)
+ if (mlp->item[j]->msgctxt != NULL)
+ has_context = true;
+ if (has_context)
+ {
+ multiline_error (xstrdup (""),
+ xstrdup (_("\
+message catalog has context dependent translations\n\
+but the C# .dll format doesn't support contexts\n")));
+ return 1;
+ }
+ }
+
retval = 1;
/* Convert the messages to Unicode. */
#include "c-ctype.h"
#include "error.h"
+#include "xerror.h"
#include "javacomp.h"
#include "message.h"
#include "mkdtemp.h"
struct expression *plural;
unsigned long int nplurals;
- header_entry = message_list_search (mlp, "");
+ header_entry = message_list_search (mlp, NULL, "");
extract_plural_expression (header_entry ? header_entry->msgstr : NULL,
&plural, &nplurals);
if (mlp->nitems == 0)
return 0;
+ /* Determine whether mlp has entries with context. */
+ {
+ bool has_context;
+ size_t j;
+
+ has_context = false;
+ for (j = 0; j < mlp->nitems; j++)
+ if (mlp->item[j]->msgctxt != NULL)
+ has_context = true;
+ if (has_context)
+ {
+ multiline_error (xstrdup (""),
+ xstrdup (_("\
+message catalog has context dependent translations\n\
+but the Java ResourceBundle format doesn't support contexts\n")));
+ return 1;
+ }
+ }
+
retval = 1;
/* Convert the messages to Unicode. */
static void
write_table (FILE *output_file, message_list_ty *mlp)
{
+ char **msgctid_arr;
size_t nstrings;
struct pre_message *msg_arr;
size_t n_sysdep_strings;
/* First pass: Move the static string pairs into an array, for sorting,
and at the same time, compute the segments of the system dependent
strings. */
+ msgctid_arr = (char **) xmalloc (mlp->nitems * sizeof (char *));
nstrings = 0;
msg_arr =
(struct pre_message *)
for (j = 0; j < mlp->nitems; j++)
{
message_ty *mp = mlp->item[j];
+ size_t msgctlen;
+ char *msgctid;
struct interval *intervals[2];
size_t nintervals[2];
+ /* Concatenate mp->msgctxt and mp->msgid into msgctid. */
+ msgctlen = (mp->msgctxt != NULL ? strlen (mp->msgctxt) + 1 : 0);
+ msgctid = (char *) xmalloc (msgctlen + strlen (mp->msgid) + 1);
+ if (mp->msgctxt != NULL)
+ {
+ memcpy (msgctid, mp->msgctxt, msgctlen - 1);
+ msgctid[msgctlen - 1] = MSGCTXT_SEPARATOR;
+ }
+ strcpy (msgctid + msgctlen, mp->msgid);
+ msgctid_arr[j] = msgctid;
+
intervals[M_ID] = NULL;
nintervals[M_ID] = 0;
intervals[M_STR] = NULL;
get_sysdep_c_format_directives (mp->msgid, false,
&intervals[M_ID], &nintervals[M_ID]);
+ if (msgctlen > 0)
+ {
+ struct interval *id_intervals = intervals[M_ID];
+ size_t id_nintervals = nintervals[M_ID];
+
+ if (id_nintervals > 0)
+ {
+ unsigned int i;
+
+ for (i = 0; i < id_nintervals; i++)
+ {
+ id_intervals[i].startpos += msgctlen;
+ id_intervals[i].endpos += msgctlen;
+ }
+ }
+ }
p_end = mp->msgstr + mp->msgstr_len;
for (p = mp->msgstr; p < p_end; p += strlen (p) + 1)
if (m == M_ID)
{
- str = mp->msgid;
- str_len = strlen (mp->msgid) + 1;
+ str = msgctid; /* concatenation of mp->msgctxt + mp->msgid */
+ str_len = strlen (msgctid) + 1;
}
else
{
else
{
/* Static string pair. */
- msg_arr[nstrings].str[M_ID].pointer = mp->msgid;
- msg_arr[nstrings].str[M_ID].length = strlen (mp->msgid) + 1;
+ msg_arr[nstrings].str[M_ID].pointer = msgctid;
+ msg_arr[nstrings].str[M_ID].length = strlen (msgctid) + 1;
msg_arr[nstrings].str[M_STR].pointer = mp->msgstr;
msg_arr[nstrings].str[M_STR].length = mp->msgstr_len;
msg_arr[nstrings].id_plural = mp->msgid_plural;
}
freea (null);
+ for (j = 0; j < mlp->nitems; j++)
+ free (msgctid_arr[j]);
free (sysdep_msg_arr);
free (msg_arr);
+ free (msgctid_arr);
}
/* Print each of the message components. Wrap them nicely so they
are as readable as possible. If there is no recorded msgstr for
this domain, emit an empty string. */
+ if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt)
+ && po_charset_canonicalize (charset) != po_charset_utf8)
+ {
+ char *warning_message =
+ xasprintf (_("\
+The following msgctxt contains non-ASCII characters.\n\
+This will cause problems to translators who use a character encoding\n\
+different from yours. Consider using a pure ASCII msgctxt instead.\n\
+%s\n"), mp->msgctxt);
+ po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
+ free (warning_message);
+ }
if (!is_ascii_string (mp->msgid)
&& po_charset_canonicalize (charset) != po_charset_utf8)
{
po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
free (warning_message);
}
+ if (mp->msgctxt != NULL)
+ wrap (mp, fp, NULL, "msgctxt", mp->msgctxt, mp->do_wrap, charset);
wrap (mp, fp, NULL, "msgid", mp->msgid, mp->do_wrap, charset);
if (mp->msgid_plural != NULL)
wrap (mp, fp, NULL, "msgid_plural", mp->msgid_plural, mp->do_wrap, charset);
/* Print each of the message components. Wrap them nicely so they
are as readable as possible. */
+ if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt)
+ && po_charset_canonicalize (charset) != po_charset_utf8)
+ {
+ char *warning_message =
+ xasprintf (_("\
+The following msgctxt contains non-ASCII characters.\n\
+This will cause problems to translators who use a character encoding\n\
+different from yours. Consider using a pure ASCII msgctxt instead.\n\
+%s\n"), mp->msgctxt);
+ po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
+ free (warning_message);
+ }
if (!is_ascii_string (mp->msgid)
&& po_charset_canonicalize (charset) != po_charset_utf8)
{
po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
free (warning_message);
}
+ if (mp->msgctxt != NULL)
+ wrap (mp, fp, "#~ ", "msgctxt", mp->msgctxt, mp->do_wrap, charset);
wrap (mp, fp, "#~ ", "msgid", mp->msgid, mp->do_wrap, charset);
if (mp->msgid_plural != NULL)
wrap (mp, fp, "#~ ", "msgid_plural", mp->msgid_plural, mp->do_wrap,
/* Search the header entry. */
header = NULL;
for (j = 0; j < mlp->nitems; ++j)
- if (mlp->item[j]->msgid[0] == '\0' && !mlp->item[j]->obsolete)
+ if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
{
header = mlp->item[j]->msgstr;
break;
message_list_ty *mlp = mdlp->item[k]->messages;
if (!(mlp->nitems == 0
- || (mlp->nitems == 1 && mlp->item[0]->msgid[0] == '\0')))
+ || (mlp->nitems == 1 && is_header (mlp->item[0]))))
{
found_nonempty = true;
break;
if (mdlp->nitems == 1)
{
message_list_ty *mlp = mdlp->item[0]->messages;
+ const lex_pos_ty *has_context;
const lex_pos_ty *has_plural;
size_t j;
+ has_context = NULL;
+ for (j = 0; j < mlp->nitems; j++)
+ {
+ message_ty *mp = mlp->item[j];
+
+ if (mp->msgctxt != NULL)
+ {
+ has_context = &mp->pos;
+ break;
+ }
+ }
+
+ if (has_context != NULL)
+ {
+ error_with_progname = false;
+ po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
+ has_context->file_name, has_context->line_number,
+ (size_t)(-1), false, _("\
+message catalog has context dependent translations, but the output format does not support them."));
+ error_with_progname = true;
+ }
+
has_plural = NULL;
for (j = 0; j < mlp->nitems; j++)
{
/* Writing Java .properties files.
- Copyright (C) 2003 Free Software Foundation, Inc.
+ Copyright (C) 2003, 2005 Free Software Foundation, Inc.
Written by Bruno Haible <bruno@clisp.org>, 2003.
This program is free software; you can redistribute it and/or modify
/* Put a comment mark if the message is the header or untranslated or
fuzzy. */
- if (mp->msgid[0] == '\0'
+ if (is_header (mp)
|| mp->msgstr[0] == '\0'
- || (mp->is_fuzzy && mp->msgid[0] != '\0'))
+ || (mp->is_fuzzy && !is_header (mp)))
putc ('!', fp);
/* Now write the untranslated string and the translated string. */
#include "utf8-ucs4.h"
#include "xalloc.h"
#include "obstack.h"
+#include "hash.h"
#include "binary-io.h"
#include "fwriteerror.h"
#include "exit.h"
A subsection can be omitted if the value to be output is the same as
for the previous record.
- In the third section, the contexts section, the data contains a hash
- table. Quite complicated.
+ The third section, the contexts section, contains the set of all occurring
+ context strings. This section is optional; it is used to speed up the
+ search. The data is a hash table with the following structure:
+ struct {
+ u16 table_size;
+ u16 buckets[table_size];
+ u8 pool[...];
+ };
+ pool[...] contains:
+ u16 zero;
+ for i = 0, ..., table_size:
+ if there are context strings with elfHash(context)%table_size == i:
+ for all context strings with elfHash(context)%table_size == i:
+ len := min(length(context),255); // truncated to length 255
+ struct {
+ u8 len;
+ u8 chars[len];
+ };
+ struct {
+ u8 zero[1]; // signals the end of this bucket
+ u8 padding[0 or 1]; // padding for even number of bytes
+ };
+ buckets[i] is 0 for an empty bucket, or the offset in pool[] where
+ the context strings for this bucket start, divided by 2.
+ This context section must not be used
+ - if the empty context is used, or
+ - if a context of length > 255 is used, or
+ - if the context pool's size would be > 2^17.
The elfHash function is the same as our hash_string function, except that
at the end it maps a hash code of 0x00000000 to 0x00000001.
{
unsigned int uc;
str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
- /* It has already been verified that the string its in ISO-8859-1. */
+ /* It has already been verified that the string fits in ISO-8859-1. */
if (!(uc < 0x100))
abort ();
/* Store as ISO-8859-1. */
message_ty *mp = mlp->item[j];
/* No need to emit the header entry, it's not needed at runtime. */
- if (mp->msgid[0] != '\0')
+ if (!is_header (mp))
{
+ char *msgctxt_as_iso_8859_1 =
+ conv_to_iso_8859_1 (mp->msgctxt != NULL ? mp->msgctxt : "");
char *msgid_as_iso_8859_1 = conv_to_iso_8859_1 (mp->msgid);
size_t msgstr_len;
unsigned short *msgstr_as_utf16 =
append_base_string (&messages_pool, msgid_as_iso_8859_1);
append_u8 (&messages_pool, 0x07);
- append_base_string (&messages_pool, "");
+ append_base_string (&messages_pool, msgctxt_as_iso_8859_1);
append_u8 (&messages_pool, 0x05);
append_u32 (&messages_pool, hashcode);
free (msgstr_as_utf16);
free (msgid_as_iso_8859_1);
+ free (msgctxt_as_iso_8859_1);
}
}
write_section (output_file, 0x69, obstack_base (&messages_pool),
obstack_object_size (&messages_pool));
- /* Omit the contexts section. */
-#if 0
- write_section (output_file, 0x2f, ...);
-#endif
+ /* Decide whether to write a contexts section. */
+ {
+ bool can_write_contexts = true;
+
+ for (j = 0; j < mlp->nitems; j++)
+ {
+ message_ty *mp = mlp->item[j];
+
+ if (!is_header (mp))
+ if (mp->msgctxt == NULL || mp->msgctxt[0] == '\0'
+ || strlen (mp->msgctxt) > 255)
+ {
+ can_write_contexts = false;
+ break;
+ }
+ }
+
+ if (can_write_contexts)
+ {
+ hash_table all_contexts;
+ size_t num_contexts;
+ unsigned long table_size;
+
+ /* Collect the contexts, removing duplicates. */
+ init_hash (&all_contexts, 10);
+ for (j = 0; j < mlp->nitems; j++)
+ {
+ message_ty *mp = mlp->item[j];
+
+ if (!is_header (mp))
+ insert_entry (&all_contexts,
+ mp->msgctxt, strlen (mp->msgctxt) + 1,
+ NULL);
+ }
+
+ /* Compute the number of different contexts. */
+ num_contexts = all_contexts.size;
+
+ /* Compute a suitable hash table size. */
+ table_size = next_prime (num_contexts * 1.7);
+ if (table_size >= 0x10000)
+ table_size = 65521;
+
+ /* Put the contexts into a hash table of size table_size. */
+ {
+ struct list_cell { const char *context; struct list_cell *next; };
+ struct list_cell *list_memory =
+ (struct list_cell *)
+ xmalloc (table_size * sizeof (struct list_cell));
+ struct list_cell *freelist;
+ struct bucket { struct list_cell *head; struct list_cell **tail; };
+ struct bucket *buckets =
+ (struct bucket *) xmalloc (table_size * sizeof (struct bucket));
+ size_t i;
+
+ freelist = list_memory;
+
+ for (i = 0; i < table_size; i++)
+ {
+ buckets[i].head = NULL;
+ buckets[i].tail = &buckets[i].head;
+ }
+
+ {
+ void *iter;
+ const void *key;
+ size_t keylen;
+ void *null;
+
+ iter = NULL;
+ while (iterate_table (&all_contexts, &iter, &key, &keylen, &null)
+ == 0)
+ {
+ const char *context = (const char *)key;
+ i = string_hashcode (context) % table_size;
+ freelist->context = context;
+ freelist->next = NULL;
+ *buckets[i].tail = freelist;
+ buckets[i].tail = &freelist->next;
+ freelist++;
+ }
+ }
+
+ /* Determine the total context pool size. */
+ {
+ size_t pool_size;
+
+ pool_size = 2;
+ for (i = 0; i < table_size; i++)
+ if (buckets[i].head != NULL)
+ {
+ const struct list_cell *p;
+
+ for (p = buckets[i].head; p != NULL; p = p->next)
+ pool_size += 1 + strlen (p->context);
+ pool_size++;
+ if ((pool_size % 2) != 0)
+ pool_size++;
+ }
+ if (pool_size <= 0x20000)
+ {
+ /* Prepare the contexts section. */
+ struct obstack contexts_pool;
+ size_t pool_offset;
+
+ obstack_init (&contexts_pool);
+
+ append_u16 (&contexts_pool, table_size);
+ pool_offset = 2;
+ for (i = 0; i < table_size; i++)
+ if (buckets[i].head != NULL)
+ {
+ const struct list_cell *p;
+
+ append_u16 (&contexts_pool, pool_offset / 2);
+ for (p = buckets[i].head; p != NULL; p = p->next)
+ pool_offset += 1 + strlen (p->context);
+ pool_offset++;
+ if ((pool_offset % 2) != 0)
+ pool_offset++;
+ }
+ else
+ append_u16 (&contexts_pool, 0);
+ if (!(pool_offset == pool_size))
+ abort ();
+
+ append_u16 (&contexts_pool, 0);
+ pool_offset = 2;
+ for (i = 0; i < table_size; i++)
+ if (buckets[i].head != NULL)
+ {
+ const struct list_cell *p;
+
+ for (p = buckets[i].head; p != NULL; p = p->next)
+ {
+ append_u8 (&contexts_pool, strlen (p->context));
+ obstack_grow (&contexts_pool,
+ p->context, strlen (p->context));
+ pool_offset += 1 + strlen (p->context);
+ }
+ append_u8 (&contexts_pool, 0);
+ pool_offset++;
+ if ((pool_offset % 2) != 0)
+ {
+ append_u8 (&contexts_pool, 0);
+ pool_offset++;
+ }
+ }
+ if (!(pool_offset == pool_size))
+ abort ();
+
+ if (!(obstack_object_size (&contexts_pool)
+ == 2 + 2 * table_size + pool_size))
+ abort ();
+
+ /* Write the contexts section. */
+ write_section (output_file, 0x2f, obstack_base (&contexts_pool),
+ obstack_object_size (&contexts_pool));
+
+ obstack_free (&contexts_pool, NULL);
+ }
+ }
+
+ free (buckets);
+ free (list_memory);
+ }
+
+ delete_hash (&all_contexts);
+ }
+ }
obstack_free (&messages_pool, NULL);
obstack_free (&hashes_pool, NULL);
/* Convert the messages to Unicode. */
iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
+ /* Determine whether mlp has non-ISO-8859-1 msgctxt entries. */
+ {
+ size_t j;
+
+ for (j = 0; j < mlp->nitems; j++)
+ {
+ const char *string = mlp->item[j]->msgctxt;
+
+ if (string != NULL)
+ {
+ /* An UTF-8 encoded string fits in ISO-8859-1 if and only if
+ all its bytes are < 0xc4. */
+ for (; *string; string++)
+ if ((unsigned char) *string >= 0xc4)
+ {
+ multiline_error (xstrdup (""),
+ xstrdup (_("\
+message catalog has msgctxt strings containing characters outside ISO-8859-1\n\
+but the Qt message catalog format supports Unicode only in the translated\n\
+strings, not in the context strings\n")));
+ return 1;
+ }
+ }
+ }
+ }
+
/* Determine whether mlp has non-ISO-8859-1 msgid entries. */
{
size_t j;
/* If no entry for this domain don't even create the file. */
if (mlp->nitems != 0)
{
+ /* Determine whether mlp has entries with context. */
+ {
+ bool has_context;
+ size_t j;
+
+ has_context = false;
+ for (j = 0; j < mlp->nitems; j++)
+ if (mlp->item[j]->msgctxt != NULL)
+ has_context = true;
+ if (has_context)
+ {
+ multiline_error (xstrdup (""),
+ xstrdup (_("\
+message catalog has context dependent translations\n\
+but the C# .resources format doesn't support contexts\n")));
+ return 1;
+ }
+ }
+
/* Determine whether mlp has plural entries. */
{
bool has_plural;
{
message_ty *mp = mlp->item[j];
- if (mp->msgid[0] == '\0')
+ if (is_header (mp))
/* Tcl's msgcat unit ignores this, but msgunfmt needs it. */
fprintf (output_file, "set ::msgcat::header ");
else
if (mlp->nitems == 0)
return 0;
+ /* Determine whether mlp has entries with context. */
+ {
+ bool has_context;
+ size_t j;
+
+ has_context = false;
+ for (j = 0; j < mlp->nitems; j++)
+ if (mlp->item[j]->msgctxt != NULL)
+ has_context = true;
+ if (has_context)
+ {
+ multiline_error (xstrdup (""),
+ xstrdup (_("\
+message catalog has context dependent translations\n\
+but the Tcl message catalog format doesn't support contexts\n")));
+ return 1;
+ }
+ }
+
/* Determine whether mlp has plural entries. */
{
bool has_plural;
static void
extract_add_message (default_po_reader_ty *this,
+ char *msgctxt,
char *msgid,
lex_pos_ty *msgid_pos,
char *msgid_plural,
bool force_fuzzy, bool obsolete)
{
/* See whether we shall exclude this message. */
- if (exclude != NULL && message_list_search (exclude, msgid) != NULL)
+ if (exclude != NULL && message_list_search (exclude, msgctxt, msgid) != NULL)
goto discard;
/* If the msgid is the empty string, it is the old header. Throw it
away, we have constructed a new one. Only remember its charset.
But if no new one was constructed, keep the old header. This is useful
because the old header may contain a charset= directive. */
- if (*msgid == '\0' && !xgettext_omit_header)
+ if (msgctxt == NULL && *msgid == '\0' && !xgettext_omit_header)
{
const char *charsetstr = strstr (msgstr, "charset=");
}
discard:
+ if (msgctxt != NULL)
+ free (msgctxt);
free (msgid);
free (msgstr);
return;
}
/* Invoke superclass method. */
- default_add_message (this, msgid, msgid_pos, msgid_plural,
+ default_add_message (this, msgctxt, msgid, msgid_pos, msgid_plural,
msgstr, msgstr_len, msgstr_pos, force_fuzzy, obsolete);
}
if (!xgettext_omit_header)
{
/* Put the old charset into the freshly constructed header entry. */
- message_ty *mp = message_list_search (mdlp->item[0]->messages, "");
+ message_ty *mp =
+ message_list_search (mdlp->item[0]->messages, NULL, "");
if (mp != NULL && !mp->obsolete)
{
static void
exclude_directive_message (abstract_po_reader_ty *pop,
+ char *msgctxt,
char *msgid,
lex_pos_ty *msgid_pos,
char *msgid_plural,
/* See if this message ID has been seen before. */
if (exclude == NULL)
exclude = message_list_alloc (true);
- mp = message_list_search (exclude, msgid);
+ mp = message_list_search (exclude, msgctxt, msgid);
if (mp != NULL)
free (msgid);
else
{
- mp = message_alloc (msgid, msgid_plural, "", 1, msgstr_pos);
+ mp = message_alloc (msgctxt, msgid, msgid_plural, "", 1, msgstr_pos);
/* Do not free msgid. */
message_list_append (exclude, mp);
}
{
enum is_format is_format[NFORMATS];
enum is_wrap do_wrap;
+ char *msgctxt;
char *msgid;
message_ty *mp;
char *msgstr;
size_t i;
+ msgctxt = NULL;
msgid = string;
/* See whether we shall exclude this message. */
- if (exclude != NULL && message_list_search (exclude, msgid) != NULL)
+ if (exclude != NULL && message_list_search (exclude, msgctxt, msgid) != NULL)
{
/* Tell the lexer to reset its comment buffer, so that the next
message gets the correct comments. */
}
/* See if we have seen this message before. */
- mp = message_list_search (mlp, msgid);
+ mp = message_list_search (mlp, msgctxt, msgid);
if (mp != NULL)
{
free (msgid);
msgstr = "";
/* Allocate a new message and append the message to the list. */
- mp = message_alloc (msgid, NULL, msgstr, strlen (msgstr) + 1, &dummypos);
+ mp = message_alloc (NULL, msgid, NULL, msgstr, strlen (msgstr) + 1,
+ &dummypos);
/* Do not free msgid. */
message_list_append (mlp, mp);
}
timestring);
free (timestring);
- mp = message_alloc ("", NULL, msgstr, strlen (msgstr) + 1, &pos);
+ mp = message_alloc (NULL, "", NULL, msgstr, strlen (msgstr) + 1, &pos);
message_comment_append (mp,
copyright_holder[0] != '\0'
if (has_plural)
{
- message_ty *header = message_list_search (mdlp->item[0]->messages, "");
+ message_ty *header =
+ message_list_search (mdlp->item[0]->messages, NULL, "");
if (header != NULL
&& strstr (header->msgstr, "Plural-Forms:") == NULL)
{
+2005-10-01 Bruno Haible <bruno@clisp.org>
+
+ Support for context dependent translations in PO files.
+ * msgattrib-15: New file.
+ * msgcat-13: New file.
+ * msgcat-14: New file.
+ * msgcomm-25: New file.
+ * msgconv-5: New file.
+ * msgen-3: New file.
+ * msgexec-4: New file.
+ * msgfilter-4: New file.
+ * msgfmt-14: New file.
+ * msgfmt-qt-2: New file.
+ * msggrep-7: New file.
+ * msginit-2: New file.
+ * msgmerge-18: New file.
+ * msguniq-5: New file.
+ * qttest2_de.po: New file.
+ * qttest2_de.qm: New file.
+ * qttest2_de.ts: New file.
+ * Makefile.am (TESTS): Add msgattrib-15, msgcat-13, msgcat-14,
+ msgcomm-25, msgconv-5, msgen-3, msgexec-4, msgfmt-14, msgfmt-qt-2,
+ msggrep-7, msginit-2, msgmerge-18, msguniq-5.
+ (EXTRA_DIST): Add qttest2_de.po, qttest2_de.qm, qttest2_de.ts.
+
2005-10-01 Bruno Haible <bruno@clisp.org>
* msgcat-12: New file.
TESTS = gettext-1 gettext-2 gettext-3 gettext-4 gettext-5 gettext-6 gettext-7 \
msgattrib-1 msgattrib-2 msgattrib-3 msgattrib-4 msgattrib-5 \
msgattrib-6 msgattrib-7 msgattrib-8 msgattrib-9 msgattrib-10 \
- msgattrib-11 msgattrib-12 msgattrib-13 msgattrib-14 \
+ msgattrib-11 msgattrib-12 msgattrib-13 msgattrib-14 msgattrib-15 \
msgattrib-properties-1 \
msgcat-1 msgcat-2 msgcat-3 msgcat-4 msgcat-5 msgcat-6 msgcat-7 \
- msgcat-8 msgcat-9 msgcat-10 msgcat-11 msgcat-12 \
+ msgcat-8 msgcat-9 msgcat-10 msgcat-11 msgcat-12 msgcat-13 msgcat-14 \
msgcat-properties-1 msgcat-properties-2 \
msgcat-stringtable-1 \
msgcmp-1 msgcmp-2 msgcmp-3 \
msgcomm-1 msgcomm-2 msgcomm-3 msgcomm-4 msgcomm-5 msgcomm-6 msgcomm-7 \
msgcomm-8 msgcomm-9 msgcomm-10 msgcomm-11 msgcomm-12 msgcomm-13 \
msgcomm-14 msgcomm-15 msgcomm-16 msgcomm-17 msgcomm-18 msgcomm-19 \
- msgcomm-20 msgcomm-21 msgcomm-22 msgcomm-23 msgcomm-24 \
- msgconv-1 msgconv-2 msgconv-3 msgconv-4 \
- msgen-1 msgen-2 \
- msgexec-1 msgexec-2 msgexec-3 \
- msgfilter-1 msgfilter-2 msgfilter-3 \
+ msgcomm-20 msgcomm-21 msgcomm-22 msgcomm-23 msgcomm-24 msgcomm-25 \
+ msgconv-1 msgconv-2 msgconv-3 msgconv-4 msgconv-5 \
+ msgen-1 msgen-2 msgen-3 \
+ msgexec-1 msgexec-2 msgexec-3 msgexec-4 \
+ msgfilter-1 msgfilter-2 msgfilter-3 msgfilter-4 \
msgfmt-1 msgfmt-2 msgfmt-3 msgfmt-4 msgfmt-5 msgfmt-6 msgfmt-7 \
- msgfmt-8 msgfmt-9 msgfmt-10 msgfmt-11 msgfmt-12 msgfmt-13 \
+ msgfmt-8 msgfmt-9 msgfmt-10 msgfmt-11 msgfmt-12 msgfmt-13 msgfmt-14 \
msgfmt-properties-1 \
msgfmt-qt-1 \
- msggrep-1 msggrep-2 msggrep-3 msggrep-4 msggrep-5 msggrep-6 \
- msginit-1 \
+ msggrep-1 msggrep-2 msggrep-3 msggrep-4 msggrep-5 msggrep-6 msggrep-7 \
+ msginit-1 msginit-2 \
msgmerge-1 msgmerge-2 msgmerge-3 msgmerge-4 msgmerge-5 msgmerge-6 \
msgmerge-7 msgmerge-8 msgmerge-9 msgmerge-10 msgmerge-11 msgmerge-12 \
msgmerge-13 msgmerge-14 msgmerge-15 msgmerge-16 msgmerge-17 \
+ msgmerge-18 \
msgmerge-compendium-1 msgmerge-compendium-2 msgmerge-compendium-3 \
msgmerge-compendium-4 \
msgmerge-properties-1 msgmerge-properties-2 \
msgunfmt-java-1 \
msgunfmt-properties-1 \
msgunfmt-tcl-1 \
- msguniq-1 msguniq-2 msguniq-3 msguniq-4 \
+ msguniq-1 msguniq-2 msguniq-3 msguniq-4 msguniq-5 \
xgettext-1 xgettext-2 xgettext-3 xgettext-4 xgettext-5 xgettext-6 \
xgettext-7 xgettext-8 \
xgettext-awk-1 \
lang-ycp lang-tcl lang-perl-1 lang-perl-2 lang-php lang-po lang-rst
EXTRA_DIST += $(TESTS) \
- test.mo xg-c-1.ok.po mex-test2.ok msguniq-a.in msguniq-a.inp \
- msguniq-a.out qttest_pl.po qttest_pl.qm ChangeLog.0 gettext-3-1.po \
- gettext-3-2.po gettext-4.po gettext-5.po gettext-6-1.po \
- gettext-6-2.po gettext-7.po
+ test.mo xg-c-1.ok.po mex-test2.ok \
+ msguniq-a.in msguniq-a.inp msguniq-a.out \
+ qttest_pl.po qttest_pl.qm \
+ qttest2_de.po qttest2_de.qm qttest2_de.ts \
+ ChangeLog.0 \
+ gettext-3-1.po gettext-3-2.po gettext-4.po gettext-5.po \
+ gettext-6-1.po gettext-6-2.po gettext-7.po
XGETTEXT = ../src/xgettext