]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
Support for context dependent translations in PO files.
authorBruno Haible <bruno@clisp.org>
Thu, 6 Oct 2005 11:20:08 +0000 (11:20 +0000)
committerBruno Haible <bruno@clisp.org>
Tue, 23 Jun 2009 10:12:52 +0000 (12:12 +0200)
48 files changed:
gettext-tools/doc/ChangeLog
gettext-tools/doc/gettext.texi
gettext-tools/doc/msgexec.texi
gettext-tools/doc/msggrep.texi
gettext-tools/lib/ChangeLog
gettext-tools/lib/gettext.h
gettext-tools/src/ChangeLog
gettext-tools/src/gettext-po.c
gettext-tools/src/gettext-po.h
gettext-tools/src/message.c
gettext-tools/src/message.h
gettext-tools/src/msgattrib.c
gettext-tools/src/msgcmp.c
gettext-tools/src/msgexec.c
gettext-tools/src/msgfilter.c
gettext-tools/src/msgfmt.c
gettext-tools/src/msggrep.c
gettext-tools/src/msginit.c
gettext-tools/src/msgl-ascii.c
gettext-tools/src/msgl-cat.c
gettext-tools/src/msgl-charset.c
gettext-tools/src/msgl-check.c
gettext-tools/src/msgl-equal.c
gettext-tools/src/msgl-iconv.c
gettext-tools/src/msgl-iconv.h
gettext-tools/src/msgmerge.c
gettext-tools/src/po-gram-gen.y
gettext-tools/src/po-lex.c
gettext-tools/src/read-mo.c
gettext-tools/src/read-po-abstract.c
gettext-tools/src/read-po-abstract.h
gettext-tools/src/read-po.c
gettext-tools/src/read-po.h
gettext-tools/src/read-properties.c
gettext-tools/src/read-stringtable.c
gettext-tools/src/read-tcl.c
gettext-tools/src/write-csharp.c
gettext-tools/src/write-java.c
gettext-tools/src/write-mo.c
gettext-tools/src/write-po.c
gettext-tools/src/write-properties.c
gettext-tools/src/write-qt.c
gettext-tools/src/write-resources.c
gettext-tools/src/write-tcl.c
gettext-tools/src/x-po.c
gettext-tools/src/xgettext.c
gettext-tools/tests/ChangeLog
gettext-tools/tests/Makefile.am

index 8bcff820a82f0870b3be8be5b9e356373632a0af..b346f549dc1d4467f77fb44057b72fe3ce14566a 100644 (file)
@@ -1,3 +1,13 @@
+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
index f897086863a5b3a6dbeebc9ff2ce4c3c0ee75e20..28d575a7e67daf755f7335116b9e67fbcbbdb0bf 100644 (file)
@@ -262,8 +262,8 @@ About @code{gettext}
 * 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
@@ -1303,6 +1303,28 @@ Likewise for Qt, see @ref{qt-format}.
 
 @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
@@ -1332,6 +1354,9 @@ msgstr[0] "s'ha trobat %d error fatal"
 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
@@ -4402,6 +4427,10 @@ With this option, each string is separately aligned so it starts at
 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
@@ -4641,8 +4670,8 @@ in using this library will be interested in this description.
 * 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
 
@@ -4798,7 +4827,7 @@ in setting the locale values is simulated by looking at the environment
 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
@@ -4846,7 +4875,129 @@ user.  If the system went out of core during the execution of
 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
 
@@ -5213,130 +5364,7 @@ Slovenian
 @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
 
index 46118327063e863b1b19b6b4899e6bfad2de7932..7177a0bf38f5f18a7fe7096ab5858c1a89fcbce1 100644 (file)
@@ -17,12 +17,15 @@ A special builtin command called @samp{0} outputs the translation, followed
 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
index d3b4eb8a9cce8019ec6d0348aed8d455e6c9a7b5..2570a2d1f4946d85a8c249d7d70ab42f3491fcc2 100644 (file)
@@ -44,13 +44,16 @@ or if it is @samp{-}.
 
 @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
@@ -83,6 +86,12 @@ either a literal file name or a wildcard pattern.
 @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}
index 6015cdccc7a8830d56ccf606a97b3f2308298b19..221b87f7e314c21234f37026ac5802ce0b7b7ed7 100644 (file)
@@ -1,3 +1,11 @@
+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.
index b896fc66246e18b28c4ce8192ec879846bebf633..9f4b123c364ed34863caf4abceda6a6fb080558c 100644 (file)
@@ -1,5 +1,5 @@
 /* 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 */
index 17b2d9b3d5f24f1975817b316a763a0ae0e9ffca..95998dfc22863d69705de029ebd7853613a9b46a 100644 (file)
@@ -1,3 +1,115 @@
+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
index 1f9bc40b9cb3da62cfcf806fe119b160e9c289e6..7a15c52fea7a6dcebf67f4aa631b9d6f4ea3f5e9 100644 (file)
@@ -304,7 +304,7 @@ po_file_domain_header (po_file_t file, const char *domain)
   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;
 
@@ -510,7 +510,36 @@ po_message_create (void)
 {
   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);
+    }
 }
 
 
@@ -1056,7 +1085,7 @@ po_message_check_all (po_message_t message, po_message_iterator_t iterator,
        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;
index 0f215c23b5abc6459c2b96fe2c7f8f58a8f2258d..b35b7c84228e28fdc8ea832f0c782bbe92551e14 100644 (file)
@@ -185,6 +185,14 @@ extern void po_message_insert (po_message_iterator_t iterator, po_message_t mess
    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);
 
index d607c5f9c7643ff6efb3f3b5beda0b685d13f5fb..d9b55785841474fcdae26166f4b9a25eeb5b7420 100644 (file)
@@ -1,5 +1,5 @@
 /* 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>
 
@@ -30,6 +30,7 @@
 #include "fstrcmp.h"
 #include "hash.h"
 #include "xalloc.h"
+#include "xallocsa.h"
 
 
 const char *const format_language[NFORMATS] =
@@ -91,7 +92,8 @@ possible_format_p (enum is_format is_format)
 
 
 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)
 {
@@ -99,6 +101,7 @@ message_alloc (const char *msgid, const char *msgid_plural,
   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;
@@ -189,7 +192,8 @@ message_copy (message_ty *mp)
   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)
@@ -245,6 +249,42 @@ message_list_free (message_list_ty *mlp)
 }
 
 
+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)
 {
@@ -259,7 +299,7 @@ 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 ();
@@ -285,7 +325,7 @@ message_list_prepend (message_list_ty *mlp, message_ty *mp)
   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 ();
@@ -311,7 +351,7 @@ message_list_insert_at (message_list_ty *mlp, size_t n, message_ty *mp)
   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 ();
@@ -375,8 +415,7 @@ message_list_msgids_changed (message_list_ty *mlp)
        {
          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.  */
@@ -392,16 +431,46 @@ message_list_msgids_changed (message_list_ty *mlp)
 
 
 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
     {
@@ -412,7 +481,10 @@ message_list_search (message_list_ty *mlp, const char *msgid)
          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;
@@ -421,7 +493,8 @@ message_list_search (message_list_ty *mlp, const char *msgid)
 
 
 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;
@@ -436,7 +509,16 @@ message_list_search_fuzzy_inner (message_list_ty *mlp, const char *msgid,
 
       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;
@@ -449,12 +531,13 @@ message_list_search_fuzzy_inner (message_list_ty *mlp, const char *msgid,
 
 
 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);
 }
 
 
@@ -513,7 +596,8 @@ message_list_list_append_list (message_list_list_ty *mllp,
 
 
 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 */
@@ -527,7 +611,7 @@ message_list_list_search (message_list_list_ty *mllp, const char *msgid)
       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);
@@ -543,7 +627,8 @@ message_list_list_search (message_list_list_ty *mllp, const char *msgid)
 
 
 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;
@@ -557,7 +642,7 @@ message_list_list_search_fuzzy (message_list_list_ty *mllp, const char *msgid)
       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;
     }
@@ -667,7 +752,8 @@ msgdomain_list_sublist (msgdomain_list_ty *mdlp, const char *domain,
 
 #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;
 
@@ -677,7 +763,7 @@ msgdomain_list_search (msgdomain_list_ty *mdlp, const char *msgid)
       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;
     }
@@ -688,7 +774,8 @@ msgdomain_list_search (msgdomain_list_ty *mdlp, const char *msgid)
 
 #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;
@@ -702,7 +789,8 @@ msgdomain_list_search_fuzzy (msgdomain_list_ty *mdlp, const char *msgid)
       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;
     }
index 604eaa993538a48994e6e71cdb534bcc18a55632..6ae559d0a564c69811ac69025712f7d127c937a9 100644 (file)
@@ -1,5 +1,5 @@
 /* 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>
 
@@ -37,6 +37,10 @@ extern "C" {
 #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
 {
@@ -96,6 +100,9 @@ enum is_wrap
 typedef struct message_ty message_ty;
 struct message_ty
 {
+  /* The msgctxt string, if present.  */
+  const char *msgctxt;
+
   /* The msgid string.  */
   const char *msgid;
 
@@ -155,9 +162,11 @@ struct message_ty
 };
 
 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
@@ -200,13 +209,16 @@ typedef bool message_predicate_ty (const message_ty *mp);
 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;
@@ -229,10 +241,10 @@ extern void
                                      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;
@@ -271,9 +283,11 @@ extern message_list_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
index c83e714da50daec1ac85207c1b0af86bb878f1b8..bffbf35568e1b8489cb52dd24b09ec947a7d9f13 100644 (file)
@@ -494,7 +494,7 @@ static bool
 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))
@@ -538,10 +538,10 @@ process_message_list (message_list_ty *mlp,
          /* 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)
@@ -549,7 +549,7 @@ process_message_list (message_list_ty *mlp,
              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;
index f86ae23ae85efee5a39260ee3ca2136343ee2875..0b535d94e6314ac78c815733abb9496ba3e20d84 100644 (file)
@@ -236,7 +236,7 @@ static bool
 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;
@@ -271,7 +271,7 @@ match_domain (const char *fn1, const char *fn2,
       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
@@ -280,7 +280,8 @@ match_domain (const char *fn1, const char *fn2,
             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, _("\
@@ -323,7 +324,7 @@ compare (const char *fn1, const char *fn2)
        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;
 
index 2090718a8a21611abe1f9ea6fcdc9286ac61f948..2a7939f2d2e42ef0438b4b9348162868d72dec5e 100644 (file)
@@ -331,6 +331,10 @@ process_string (const message_ty *mp, const char *str, size_t len)
       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);
index f5a0a9267ebeddadf4d512fe4016622fc7ab8804..ef01b46cbe6dd588d373211f5183240cb974b465 100644 (file)
@@ -724,7 +724,7 @@ process_message (message_ty *mp)
   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.  */
index cc88ed66236a4eee32b2b9216ea7b292dbb01c68..7107b4a6060be4c4fd48f4e09904b2405807fe4e 100644 (file)
@@ -913,6 +913,7 @@ domain name \"%s\" not suitable as file name: will use prefix"), name);
 
 static void
 msgfmt_add_message (default_po_reader_ty *this,
+                   char *msgctxt,
                    char *msgid,
                    lex_pos_ty *msgid_pos,
                    char *msgid_plural,
@@ -932,7 +933,7 @@ msgfmt_add_message (default_po_reader_ty *this,
     }
 
   /* 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);
 }
 
@@ -950,7 +951,7 @@ msgfmt_frob_new_message (default_po_reader_ty *that, message_ty *mp,
         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)
            {
@@ -973,7 +974,7 @@ msgfmt_frob_new_message (default_po_reader_ty *that, message_ty *mp,
       else
        {
          /* Test for header entry.  */
-         if (mp->msgid[0] == '\0')
+         if (is_header (mp))
            {
              this->has_header_entry = true;
              if (!mp->is_fuzzy)
index aa639049f225953323584cd2a1ee95b454b0776e..9f45bdc9b211fbecea73df1db0160f7733130c34 100644 (file)
@@ -78,7 +78,7 @@ struct grep_task {
   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[] =
@@ -96,6 +96,7 @@ 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 },
@@ -170,7 +171,7 @@ main (int argc, char **argv)
   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];
 
@@ -181,7 +182,7 @@ main (int argc, char **argv)
       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)
@@ -190,7 +191,7 @@ main (int argc, char **argv)
        break;
 
       case 'C':
-       grep_pass = 2;
+       grep_pass = 3;
        break;
 
       case 'D':
@@ -283,10 +284,14 @@ error while reading \"%s\""), optarg);
        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;
@@ -312,7 +317,7 @@ error while reading \"%s\""), optarg);
        break;
 
       case 'T':
-       grep_pass = 1;
+       grep_pass = 2;
        break;
 
       case 'V':
@@ -405,7 +410,7 @@ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
           "--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];
 
@@ -428,7 +433,8 @@ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
 
   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);
@@ -454,7 +460,7 @@ static void
 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);
 }
@@ -503,9 +509,11 @@ or if it is -.\n"));
       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\
@@ -513,13 +521,14 @@ 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\
@@ -650,7 +659,7 @@ is_message_selected (const message_ty *mp)
   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.  */
@@ -658,11 +667,16 @@ is_message_selected (const message_ty *mp)
     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.  */
@@ -673,14 +687,14 @@ is_message_selected (const message_ty *mp)
     {
       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;
@@ -706,7 +720,7 @@ is_message_selected (const message_ty *mp)
       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);
 
index 2856ba88956ce231e5099412468a0ff7e4724850..1fbad5ce62ed9bd61c258c32432960ab0acf2975 100644 (file)
@@ -1715,7 +1715,7 @@ fill_header (msgdomain_list_ty *mdlp)
 
          /* 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;
@@ -1726,7 +1726,7 @@ fill_header (msgdomain_list_ty *mdlp)
            {
              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);
            }
 
@@ -1797,7 +1797,7 @@ update_msgstr_plurals (msgdomain_list_ty *mdlp)
       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);
index db3a6cf45fe895414bed34dfc70826487a6d8b0d..d1e4bd3ea7ee3ea9c6171af16462754735609b57 100644 (file)
@@ -73,6 +73,10 @@ is_ascii_message (message_ty *mp)
   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;
 }
 
index c125832a5cd7b3954f81360acc25f076ec1efdad..d46dff545e48df11bcf51e1d313e21ce6797c5a0 100644 (file)
@@ -69,7 +69,7 @@ is_message_selected (const message_ty *tmp)
 {
   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));
 }
@@ -79,7 +79,7 @@ static bool
 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
@@ -137,7 +137,7 @@ catenate_msgdomain_list (string_list_ty *file_list, const char *to_code)
          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;
 
@@ -230,7 +230,7 @@ domain \"%s\" in input file `%s' doesn't contain a header entry with a charset s
          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;
 
@@ -296,11 +296,11 @@ domain \"%s\" in input file `%s' doesn't contain a header entry with a charset s
              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 */
@@ -312,7 +312,7 @@ domain \"%s\" in input file `%s' doesn't contain a header entry with a charset s
                }
 
              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.  */
                {
@@ -477,8 +477,8 @@ To select a different output encoding, use the --to-code option.\n\
                  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));
index 7f31076507d76dec801ad8753713199d26b0e64b..db65795d294dd1b4ba0ac1b68f305bcd14a7b108 100644 (file)
@@ -1,5 +1,5 @@
 /* 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
@@ -60,7 +60,8 @@ compare_po_locale_charsets (const msgdomain_list_ty *mdlp)
       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;
 
index 619b6a28561ca2ef3ae58dddeb7ff6362606019a..f741825a7b2ef8e34f021aa93fae98be79bfe287 100644 (file)
@@ -280,7 +280,7 @@ check_plural (message_list_ty *mlp)
 
   /* 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;
@@ -728,7 +728,7 @@ check_message (const message_ty *mp,
               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,
index 3be8cac426e0ed626f1b1ef6ff4500e4405e4b0f..69a2602520436c77a96489104cbd84429c622e1b 100644 (file)
@@ -1,5 +1,5 @@
 /* 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
@@ -136,6 +136,11 @@ message_equal (const message_ty *mp1, const message_ty *mp2,
 {
   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;
 
@@ -145,7 +150,7 @@ message_equal (const message_ty *mp1, const message_ty *mp2,
        : 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,
index 315a7124b79f91389eea937034c4aa0b63268c73..4edb9bed084f418dd4ff0c1ecd9a5d55ff8dc541 100644 (file)
@@ -228,6 +228,8 @@ static void
 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);
@@ -290,7 +292,7 @@ iconv_message_list (message_list_ty *mlp,
 
   /* 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;
 
@@ -402,7 +404,8 @@ and iconv() does not support this conversion."),
        {
          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);
index e0f340cb03e7339589800c52fc0674f6323009fa..685a389e7c8ea8e4a94e0007011c15a9eac83985 100644 (file)
@@ -53,7 +53,7 @@ extern char *convert_string (iconv_t cd, const char *string,
    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,
index b114e4122cefea110dd5b81f173e63f3aa08af67..afe5b78325e376791f825c559155fce911461849 100644 (file)
@@ -631,7 +631,7 @@ message_merge (message_ty *def, message_ty *ref)
      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
@@ -833,7 +833,8 @@ message_merge (message_ty *def, message_ty *ref)
       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
@@ -915,7 +916,7 @@ match_domain (const char *fn1, const char *fn2,
   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);
@@ -933,7 +934,8 @@ match_domain (const char *fn1, const char *fn2,
       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
@@ -949,7 +951,7 @@ match_domain (const char *fn1, const char *fn2,
          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
@@ -957,6 +959,7 @@ match_domain (const char *fn1, const char *fn2,
          if (use_fuzzy_matching
              && ((defmsg =
                     message_list_list_search_fuzzy (definitions,
+                                                    refmsg->msgctxt,
                                                     refmsg->msgid)) != NULL))
            {
              message_ty *mp;
@@ -1044,7 +1047,8 @@ this message is used but not defined in %s"), fn1);
        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
@@ -1135,10 +1139,10 @@ merge (const char *fn1, const char *fn2, msgdomain_list_ty **defp)
   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);
       }
@@ -1152,7 +1156,7 @@ merge (const char *fn1, const char *fn2, msgdomain_list_ty **defp)
        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;
 
index 6827906c2244992832c7138b5c0bc8dc4ae0825e..042db110055fa06888bb86b7b33ddd7c91515205 100644 (file)
@@ -1,5 +1,5 @@
 /* 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>
 
@@ -94,15 +94,17 @@ static long plural_counter;
     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);
 }
@@ -112,6 +114,7 @@ do_callback_message (char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural,
 %token COMMENT
 %token DOMAIN
 %token JUNK
+%token MSGCTXT
 %token MSGID
 %token MSGID_PLURAL
 %token MSGSTR
@@ -129,10 +132,10 @@ do_callback_message (char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural,
   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
@@ -155,7 +158,7 @@ domain
        ;
 
 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);
@@ -164,7 +167,7 @@ message
                  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
@@ -173,7 +176,7 @@ message
                      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);
 
@@ -181,7 +184,7 @@ message
                  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
@@ -191,7 +194,7 @@ message
                      free ($4.rhs.msgstr);
                    }
                }
-       | MSGID string_list msgid_pluralform
+       | message_intro string_list msgid_pluralform
                {
                  check_obsolete ($1, $2);
                  check_obsolete ($1, $3);
@@ -199,7 +202,7 @@ message
                  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);
@@ -207,7 +210,7 @@ message
                  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"));
@@ -215,6 +218,23 @@ message
                }
        ;
 
+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
                {
index 9fc38b585754d645c9949d16471d36d685b6bde7..a4c26937161ad1ded77947d9e120b2dd1ec32f53 100644 (file)
@@ -50,6 +50,7 @@
 #include "po-error.h"
 #include "po-xerror.h"
 #include "pos.h"
+#include "message.h"
 #include "str-list.h"
 #include "po-gram-gen2.h"
 
@@ -725,6 +726,8 @@ keyword_p (const char *s)
     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;
 }
@@ -966,6 +969,12 @@ po_gram_lex ()
              }
            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;
index 4efcffdca6a8df5ace665ab7ceb7b257f8283f01..f0684eb59e06cab9a2fdb899727a3b189b66af3b 100644 (file)
@@ -1,5 +1,5 @@
 /* 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
@@ -286,20 +286,35 @@ read_mo_file (message_list_ty *mlp, const char *filename)
       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),
@@ -327,21 +342,36 @@ read_mo_file (message_list_ty *mlp, const char *filename)
          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),
index bb9974034d37cfec7d65c2969d61dbd37be15f50..fa8fe37041973e3c9cb6740c9019c541ff76c66f 100644 (file)
@@ -93,6 +93,7 @@ call_directive_domain (abstract_po_reader_ty *pop, char *name)
 
 static inline void
 call_directive_message (abstract_po_reader_ty *pop,
+                       char *msgctxt,
                        char *msgid,
                        lex_pos_ty *msgid_pos,
                        char *msgid_plural,
@@ -101,7 +102,8 @@ call_directive_message (abstract_po_reader_ty *pop,
                        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);
 }
@@ -216,12 +218,14 @@ po_callback_domain (char *name)
 /* 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);
 }
index ebc3ba229fd5a95f7b181f714a0eae3efa6987cc..8026bffd1ed42ab71be8cb6e775f67a1d753cca0 100644 (file)
@@ -75,6 +75,7 @@ struct abstract_po_reader_class_ty
 
   /* 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,
@@ -153,7 +154,8 @@ extern void
 
 /* 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,
index 4b3708147193dc6b277befbb1b7b596b63b2a9d7..4fea79b15963e1c176ece3840550bb442030814b 100644 (file)
@@ -51,6 +51,7 @@ call_set_domain (struct default_po_reader_ty *this, char *name)
 
 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)
@@ -59,7 +60,8 @@ call_add_message (struct default_po_reader_ty *this,
     (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);
 }
@@ -225,9 +227,10 @@ default_directive_domain (abstract_po_reader_ty *that, char *name)
 }
 
 
-/* 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,
@@ -237,7 +240,7 @@ default_directive_message (abstract_po_reader_ty *that,
 {
   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.  */
@@ -325,6 +328,7 @@ default_set_domain (default_po_reader_ty *this, char *name)
 
 void
 default_add_message (default_po_reader_ty *this,
+                    char *msgctxt,
                     char *msgid,
                     lex_pos_ty *msgid_pos,
                     char *msgid_plural,
@@ -343,7 +347,7 @@ default_add_message (default_po_reader_ty *this,
     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)
     {
@@ -365,6 +369,8 @@ default_add_message (default_po_reader_ty *this,
         (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);
@@ -375,7 +381,8 @@ default_add_message (default_po_reader_ty *this,
         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)
index 4d307ebb54a391de838fa6d391543ae132ea6770..c1065a1c274916d9e5649489b6bdbb51663cf2eb 100644 (file)
@@ -1,5 +1,5 @@
 /* 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
@@ -56,6 +56,7 @@ struct default_po_reader_class_ty
 
   /* 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);
@@ -122,6 +123,7 @@ extern void default_parse_brief (abstract_po_reader_ty *that);
 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,
@@ -136,6 +138,7 @@ extern void default_comment_special (abstract_po_reader_ty *that,
                                     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,
index 4e22d9eba51218f63cb6ae99f5276dddc355b669..9b853a8725220f1302d652a0844bed3952a2313e 100644 (file)
@@ -536,7 +536,7 @@ properties_parse (abstract_po_reader_ty *this, FILE *file,
                 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);
            }
index 081e5829dc1cdc5803b2ec7eda63e1115e41cd11..59f78e4658f8e75d4aba8dfc2eba85cbb031d4a0 100644 (file)
@@ -879,7 +879,7 @@ stringtable_parse (abstract_po_reader_ty *pop, FILE *file,
             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);
        }
@@ -928,7 +928,7 @@ stringtable_parse (abstract_po_reader_ty *pop, FILE *file,
                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);
            }
index 64fedc5776d1e4eae4c638c5a75f542518789f6e..f101570b7f0d00127eb949a7498a5500cab3bcea 100644 (file)
@@ -1,5 +1,5 @@
 /* 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
@@ -137,7 +137,7 @@ msgdomain_read_tcl (const char *locale_name, const char *directory)
       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)
index 80143d8512297cd549bfcac5c7c29d15fe496226..611879b19d417a33dac6058e47f7ee1729d7119f 100644 (file)
@@ -85,8 +85,9 @@
 #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"
@@ -562,7 +563,7 @@ write_csharp_code (FILE *stream, const char *class_name, message_list_ty *mlp)
       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);
 
@@ -636,6 +637,25 @@ msgdomain_write_csharp (message_list_ty *mlp, const char *canon_encoding,
   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.  */
index c645b8b231116f9ca200431dbe38c58da1cd29ce..b197cd866f48176d3963bbe037d1a6cb313b0c41 100644 (file)
@@ -69,6 +69,7 @@
 
 #include "c-ctype.h"
 #include "error.h"
+#include "xerror.h"
 #include "javacomp.h"
 #include "message.h"
 #include "mkdtemp.h"
@@ -873,7 +874,7 @@ write_java_code (FILE *stream, const char *class_name, message_list_ty *mlp,
       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);
 
@@ -957,6 +958,25 @@ msgdomain_write_java (message_list_ty *mlp, const char *canon_encoding,
   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.  */
index 8fdba911f25dbac2282f85fc1900f45952f820be..8c4cf1c4776c70dea762d9d712d9c7285c261168 100644 (file)
@@ -145,6 +145,7 @@ struct pre_sysdep_message
 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;
@@ -169,6 +170,7 @@ write_table (FILE *output_file, message_list_ty *mlp)
   /* 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 *)
@@ -183,9 +185,22 @@ write_table (FILE *output_file, message_list_ty *mlp)
   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;
@@ -204,6 +219,22 @@ write_table (FILE *output_file, message_list_ty *mlp)
 
          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)
@@ -252,8 +283,8 @@ write_table (FILE *output_file, message_list_ty *mlp)
 
              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
                {
@@ -321,8 +352,8 @@ write_table (FILE *output_file, message_list_ty *mlp)
       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;
@@ -745,8 +776,11 @@ write_table (FILE *output_file, message_list_ty *mlp)
     }
 
   freea (null);
+  for (j = 0; j < mlp->nitems; j++)
+    free (msgctid_arr[j]);
   free (sysdep_msg_arr);
   free (msg_arr);
+  free (msgctid_arr);
 }
 
 
index 12250b7633c3b7d117d53b7a62ba1b0094154dce..89adbf844935a734c2edae35569d3d9bf88bfb0e 100644 (file)
@@ -854,6 +854,18 @@ message_print (const message_ty *mp, FILE *fp, const char *charset,
   /* 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)
     {
@@ -866,6 +878,8 @@ different from yours. Consider using a pure ASCII msgid instead.\n\
       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);
@@ -930,6 +944,18 @@ message_print_obsolete (const message_ty *mp, FILE *fp, const char *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)
     {
@@ -942,6 +968,8 @@ different from yours. Consider using a pure ASCII msgid instead.\n\
       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,
@@ -997,7 +1025,7 @@ msgdomain_list_print_po (msgdomain_list_ty *mdlp, FILE *fp, bool debug)
       /* 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;
@@ -1066,7 +1094,7 @@ msgdomain_list_print (msgdomain_list_ty *mdlp, const char *filename,
          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;
@@ -1092,9 +1120,32 @@ Cannot output multiple translation domains into a single file with NeXTstep/GNUs
       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++)
            {
index 4663cfa7e268d8ac01c28edbf7d5479b61d8aed6..0e74f20532b28f6a65d31c0ea0b022aae41bdb9a 100644 (file)
@@ -1,5 +1,5 @@
 /* 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
@@ -219,9 +219,9 @@ write_message (FILE *fp, const message_ty *mp, size_t page_width, bool debug)
 
   /* 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.  */
index e24db29d64ba4bfff234ad26bd41b1b671bf9976..c4ee7b65447627d3906ab910107ea33c9a8a7373 100644 (file)
@@ -39,6 +39,7 @@
 #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.
@@ -270,7 +297,7 @@ conv_to_iso_8859_1 (const char *string)
     {
       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.  */
@@ -386,8 +413,10 @@ write_qm (FILE *output_file, message_list_ty *mlp)
       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 =
@@ -411,7 +440,7 @@ write_qm (FILE *output_file, message_list_ty *mlp)
          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);
@@ -420,6 +449,7 @@ write_qm (FILE *output_file, message_list_ty *mlp)
 
          free (msgstr_as_utf16);
          free (msgid_as_iso_8859_1);
+         free (msgctxt_as_iso_8859_1);
        }
     }
 
@@ -441,10 +471,176 @@ write_qm (FILE *output_file, message_list_ty *mlp)
   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);
@@ -482,6 +678,32 @@ but the Qt message catalog format doesn't support plural handling\n")));
       /* 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;
index 9744b8bdac54c1af6d749ead1c0cb53ae7d70d00..e8463312cf08cd8c206a7a6d90a94f61473cff78 100644 (file)
@@ -117,6 +117,25 @@ msgdomain_write_csharp_resources (message_list_ty *mlp,
   /* 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;
index 59fff245dbf140bda3264c41585dc2647a73130e..0821cc7a7d29cba9b4ea3385d6bbb36e2b8c715a 100644 (file)
@@ -120,7 +120,7 @@ write_msg (FILE *output_file, message_list_ty *mlp, const char *locale_name)
     {
       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
@@ -143,6 +143,25 @@ msgdomain_write_tcl (message_list_ty *mlp, const char *canon_encoding,
   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;
index caa3a2a75f72e9abb32a51859f8b7c7496a99ffd..f57ab70b1bbeb384085d7208a3b8d3efaf6eb6ff 100644 (file)
@@ -47,6 +47,7 @@ static char *header_charset;
 
 static void
 extract_add_message (default_po_reader_ty *this,
+                    char *msgctxt,
                     char *msgid,
                     lex_pos_ty *msgid_pos,
                     char *msgid_plural,
@@ -55,14 +56,14 @@ extract_add_message (default_po_reader_ty *this,
                     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=");
 
@@ -83,13 +84,15 @@ extract_add_message (default_po_reader_ty *this,
        }
 
      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);
 }
 
@@ -148,7 +151,8 @@ extract (FILE *fp,
       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)
            {
index 8bfa8b1629fcb8a54cdbae59d2d71a59bb599134..c9c9fa0be6d83268fd3eb09abb9cfc9001fd6c2f 100644 (file)
@@ -886,6 +886,7 @@ exclude_directive_domain (abstract_po_reader_ty *pop, char *name)
 
 static void
 exclude_directive_message (abstract_po_reader_ty *pop,
+                          char *msgctxt,
                           char *msgid,
                           lex_pos_ty *msgid_pos,
                           char *msgid_plural,
@@ -898,12 +899,12 @@ exclude_directive_message (abstract_po_reader_ty *pop,
   /* 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);
     }
@@ -1761,15 +1762,17 @@ remember_a_message (message_list_ty *mlp, char *string,
 {
   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.  */
@@ -1803,7 +1806,7 @@ meta information, not the empty string.\n")));
     }
 
   /* 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);
@@ -1829,7 +1832,8 @@ meta information, not the empty string.\n")));
        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);
     }
@@ -2102,7 +2106,7 @@ Content-Transfer-Encoding: 8bit\n",
                      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'
@@ -2152,7 +2156,8 @@ finalize_header (msgdomain_list_ty *mdlp)
 
     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)
          {
index c42d00e479ad0c670b5cda702794fd5344de8758..3de68d0844af0d12e6db98c6af775d2c46cd8640 100644 (file)
@@ -1,3 +1,28 @@
+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.
index 1cd274450b1be6168e714045a997cbde139b0e5d..1894552406bbfebdb7369295933d104b28747d52 100644 (file)
@@ -23,30 +23,31 @@ EXTRA_DIST =
 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 \
@@ -56,7 +57,7 @@ TESTS = gettext-1 gettext-2 gettext-3 gettext-4 gettext-5 gettext-6 gettext-7 \
        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 \
@@ -109,10 +110,13 @@ TESTS = gettext-1 gettext-2 gettext-3 gettext-4 gettext-5 gettext-6 gettext-7 \
        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