diff options
author | Bruno Haible <bruno@clisp.org> | 2001-10-25 09:38:30 +0000 |
---|---|---|
committer | Bruno Haible <bruno@clisp.org> | 2001-10-25 09:38:30 +0000 |
commit | 72d14ef6b0e15298ab77229c71a732e73c741273 (patch) | |
tree | 0990acce2cf3ed576569aa37cb32aded422f519b | |
parent | 0221e6d7d6aba165d4018a3f094191feee731b6a (diff) | |
download | external_gettext-72d14ef6b0e15298ab77229c71a732e73c741273.zip external_gettext-72d14ef6b0e15298ab77229c71a732e73c741273.tar.gz external_gettext-72d14ef6b0e15298ab77229c71a732e73c741273.tar.bz2 |
New program 'msginit'.
-rw-r--r-- | man/ChangeLog | 9 | ||||
-rw-r--r-- | man/Makefile.am | 11 | ||||
-rw-r--r-- | man/msginit.x | 4 | ||||
-rw-r--r-- | src/ChangeLog | 14 | ||||
-rw-r--r-- | src/Makefile.am | 10 | ||||
-rw-r--r-- | src/msginit.c | 1563 | ||||
-rwxr-xr-x | src/project-id | 67 |
7 files changed, 1672 insertions, 6 deletions
diff --git a/man/ChangeLog b/man/ChangeLog index e9b1e81..113e569 100644 --- a/man/ChangeLog +++ b/man/ChangeLog @@ -1,3 +1,12 @@ +2001-10-08 Bruno Haible <haible@clisp.cons.org> + + * msginit.x: New file. + * Makefile.am (man_aux): Add msginit.x. + (man_MAN1OTHER): Add msginit.1. + (man_HTMLOTHER): Add msginit.1.html. + (msginit.1): New rule. + (msginit.1.html): New rule. + 2001-09-08 Bruno Haible <haible@clisp.cons.org> * msgattrib.x: New file. diff --git a/man/Makefile.am b/man/Makefile.am index 0d66e87..8de6601 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -30,7 +30,7 @@ AUTOMAKE_OPTIONS = 1.2 gnits man_aux = gettext.x ngettext.x \ msgcmp.x msgfmt.x msgmerge.x msgunfmt.x xgettext.x \ -msgattrib.x msgcat.x msgcomm.x msgconv.x msgen.x msgexec.x msggrep.x msguniq.x +msgattrib.x msgcat.x msgcomm.x msgconv.x msgen.x msgexec.x msggrep.x msginit.x msguniq.x # Likewise, plus additional manual pages for the libintl functions. @@ -38,7 +38,7 @@ man_MAN1GEN = gettext.1 ngettext.1 man_MAN1IN = gettext.1.in ngettext.1.in man_MAN1OTHER = \ msgcmp.1 msgfmt.1 msgmerge.1 msgunfmt.1 xgettext.1 \ -msgattrib.1 msgcat.1 msgcomm.1 msgconv.1 msgen.1 msgexec.1 msggrep.1 msguniq.1 +msgattrib.1 msgcat.1 msgcomm.1 msgconv.1 msgen.1 msgexec.1 msggrep.1 msginit.1 msguniq.1 man_MAN1 = $(man_MAN1GEN) $(man_MAN1OTHER) man_MAN3 = gettext.3 ngettext.3 \ textdomain.3 bindtextdomain.3 bind_textdomain_codeset.3 @@ -51,7 +51,7 @@ man_HTMLGEN = gettext.1.html ngettext.1.html man_HTMLIN = gettext.1.html.in ngettext.1.html.in man_HTMLOTHER = \ msgcmp.1.html msgfmt.1.html msgmerge.1.html msgunfmt.1.html xgettext.1.html \ -msgattrib.1.html msgcat.1.html msgcomm.1.html msgconv.1.html msgen.1.html msgexec.1.html msggrep.1.html msguniq.1.html \ +msgattrib.1.html msgcat.1.html msgcomm.1.html msgconv.1.html msgen.1.html msgexec.1.html msggrep.1.html msginit.1.html msguniq.1.html \ gettext.3.html ngettext.3.html \ textdomain.3.html bindtextdomain.3.html bind_textdomain_codeset.3.html man_HTML = $(man_HTMLGEN) $(man_HTMLOTHER) @@ -122,6 +122,8 @@ msgexec.1: msgexec.x $(SHELL) x-to-1 "$(PERL)" "$(HELP2MAN)" ../src/msgexec$(EXEEXT) $(srcdir)/msgexec.x msgexec.1 msggrep.1: msggrep.x $(SHELL) x-to-1 "$(PERL)" "$(HELP2MAN)" ../src/msggrep$(EXEEXT) $(srcdir)/msggrep.x msggrep.1 +msginit.1: msginit.x + $(SHELL) x-to-1 "$(PERL)" "$(HELP2MAN)" ../src/msginit$(EXEEXT) $(srcdir)/msginit.x msginit.1 msguniq.1: msguniq.x $(SHELL) x-to-1 "$(PERL)" "$(HELP2MAN)" ../src/msguniq$(EXEEXT) $(srcdir)/msguniq.x msguniq.1 @@ -199,6 +201,9 @@ msgexec.1.html: msgexec.1 msggrep.1.html: msggrep.1 $(MAN2HTML) `if test -f msggrep.1; then echo .; else echo $(srcdir); fi`/msggrep.1 | sed -e '/CreationDate:/d' > t-$@ mv t-$@ $@ +msginit.1.html: msginit.1 + $(MAN2HTML) `if test -f msginit.1; then echo .; else echo $(srcdir); fi`/msginit.1 | sed -e '/CreationDate:/d' > t-$@ + mv t-$@ $@ msguniq.1.html: msguniq.1 $(MAN2HTML) `if test -f msguniq.1; then echo .; else echo $(srcdir); fi`/msguniq.1 | sed -e '/CreationDate:/d' > t-$@ mv t-$@ $@ diff --git a/man/msginit.x b/man/msginit.x new file mode 100644 index 0000000..126791f --- /dev/null +++ b/man/msginit.x @@ -0,0 +1,4 @@ +[NAME] +msginit \- initialize a message catalog +[DESCRIPTION] +.\" Add any additional description here diff --git a/src/ChangeLog b/src/ChangeLog index 3b6f05a..b174aab 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,5 +1,17 @@ 2001-10-21 Bruno Haible <haible@clisp.cons.org> + * msginit.c: New file. + * project-id: New file. + * Makefile.am (bin_PROGRAMS): Add msginit. + (projectsdir): New variable. + (DEFS): Add -DLIBDIR and -DPROJECTSDIR. + (msginit_SOURCES): New variable. + (msginit_LDADD): New variable. + (install-exec-local): Also install project-id. + (uninstall-local): Also uninstall project-id. + +2001-10-21 Bruno Haible <haible@clisp.cons.org> + * gnu/gettext/GetURL.java: New file. * urlget.c: New file. * Makefile.am (noinst_PROGRAMS): Add urlget. @@ -51,7 +63,7 @@ * read-java.c (execute_and_read_po_output): Update for changed create_pipe_in(), wait_subprocess(). -2001-01-08 Bruno Haible <haible@clisp.cons.org> +2001-10-08 Bruno Haible <haible@clisp.cons.org> * xgettext.c (remember_a_message): When the comment tag is seen, remember all remaining comment lines, not just one. diff --git a/src/Makefile.am b/src/Makefile.am index ba1930a..ce0cf5b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -21,7 +21,7 @@ AUTOMAKE_OPTIONS = 1.5 gnits no-dependencies bin_PROGRAMS = gettext ngettext \ msgcmp msgfmt msgmerge msgunfmt xgettext \ -msgattrib msgcat msgcomm msgconv msgen msgexec msggrep msguniq +msgattrib msgcat msgcomm msgconv msgen msgexec msggrep msginit msguniq noinst_PROGRAMS = hostname urlget @@ -35,11 +35,12 @@ EXTRA_DIST = FILES localedir = $(datadir)/locale jardir = $(datadir)/gettext +projectsdir = $(pkgdatadir)/projects INCLUDES = -I. -I$(srcdir) -I.. -I$(top_srcdir)/lib -I../intl \ -I$(top_srcdir)/intl DEFS = -DLOCALEDIR=\"$(localedir)\" -DGETTEXTJAR=\"$(jardir)/gettext.jar\" \ -@DEFS@ +-DLIBDIR=\"$(libdir)\" -DPROJECTSDIR=\"$(projectsdir)\" @DEFS@ LDADD = ../lib/libnlsut.a @INTLLIBS@ SED = sed @@ -79,6 +80,7 @@ msgconv_SOURCES = msgconv.c $(COMMON_SOURCE) msgl-ascii.c write-po.c read-po msgen_SOURCES = msgen.c $(COMMON_SOURCE) msgl-ascii.c write-po.c read-po.c msgl-english.c msgexec_SOURCES = msgexec.c $(COMMON_SOURCE) msgl-ascii.c write-po.c read-po.c msgl-charset.c msggrep_SOURCES = msggrep.c $(COMMON_SOURCE) msgl-ascii.c write-po.c read-po.c msgl-charset.c +msginit_SOURCES = msginit.c $(COMMON_SOURCE) msgl-ascii.c write-po.c read-po.c msgl-english.c po-time.c msguniq_SOURCES = msguniq.c $(COMMON_SOURCE) msgl-ascii.c write-po.c read-po.c msgl-iconv.c msgl-cat.c hostname_SOURCES = hostname.c urlget_SOURCES = urlget.c @@ -98,6 +100,8 @@ msgconv_LDADD = ../lib/libnlsut.a @INTLLIBS@ @LIBICONV@ msgen_LDADD = ../lib/libnlsut.a @INTLLIBS@ @LIBICONV@ msgexec_LDADD = ../lib/libnlsut.a @INTLLIBS@ @LIBICONV@ msggrep_LDADD = ../lib/libnlsut.a @INTLLIBS@ @LIBICONV@ +msginit_LDADD = ../intl/localealias.$(OBJEXT) ../intl/localename.$(OBJEXT) \ + ../lib/libnlsut.a @INTLLIBS@ @LIBICONV@ msguniq_LDADD = ../lib/libnlsut.a @INTLLIBS@ @LIBICONV@ @@ -124,6 +128,7 @@ install-exec-local: $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(INSTALL_PROGRAM) hostname$(EXEEXT) $(DESTDIR)$(libdir)/$(PACKAGE)/hostname$(EXEEXT) $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(INSTALL_PROGRAM) urlget$(EXEEXT) $(DESTDIR)$(libdir)/$(PACKAGE)/urlget$(EXEEXT) $(INSTALL_SCRIPT) user-email $(DESTDIR)$(libdir)/$(PACKAGE)/user-email + $(INSTALL_SCRIPT) $(srcdir)/project-id $(DESTDIR)$(libdir)/$(PACKAGE)/project-id installdirs-local: $(mkinstalldirs) $(DESTDIR)$(libdir)/$(PACKAGE) @@ -132,6 +137,7 @@ uninstall-local: $(RM) $(DESTDIR)$(libdir)/$(PACKAGE)/hostname$(EXEEXT) $(RM) $(DESTDIR)$(libdir)/$(PACKAGE)/urlget$(EXEEXT) $(RM) $(DESTDIR)$(libdir)/$(PACKAGE)/user-email + $(RM) $(DESTDIR)$(libdir)/$(PACKAGE)/project-id # Special rules for Java compilation. diff --git a/src/msginit.c b/src/msginit.c new file mode 100644 index 0000000..3296a6f --- /dev/null +++ b/src/msginit.c @@ -0,0 +1,1563 @@ +/* Initializes a new PO file. + Copyright (C) 2001 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 + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <errno.h> +#include <getopt.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sys/types.h> +#include <pwd.h> + +#if HAVE_UNISTD_H +# include <unistd.h> +#endif + +#if HAVE_DIRENT_H +# include <dirent.h> +#else +# define dirent direct +# if HAVE_SYS_NDIR_H +# include <sys/ndir.h> +# endif +# if HAVE_SYS_DIR_H +# include <sys/dir.h> +# endif +# if HAVE_NDIR_H +# include <ndir.h> +# endif +#endif + +#if CLOSEDIR_VOID +/* Fake a return value. */ +# define CLOSEDIR(d) (closedir (d), 0) +#else +# define CLOSEDIR(d) closedir (d) +#endif + +#if HAVE_DIRENT_H || HAVE_NDIR_H || HAVE_SYS_DIR_H || HAVE_SYS_NDIR_H +# define HAVE_DIR 1 +#else +# define HAVE_DIR 0 +#endif + +#include "error.h" +#include "progname.h" +#include "basename.h" +#include "strpbrk.h" +#include "message.h" +#include "read-po.h" +#include "write-po.h" +#include "po-charset.h" +#include "po-time.h" +#include "xmalloc.h" +#include "system.h" +#include "xerror.h" +#include "msgl-english.h" +#include "pipe.h" +#include "wait-process.h" +#include "getline.h" +#include "setenv.h" +#include "str-list.h" +#include "libgettext.h" + +#define _(str) gettext (str) +#define N_(str) (str) + +#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) + +extern const char * locale_charset PARAMS ((void)); +extern const char * _nl_locale_name PARAMS ((int category, + const char *categoryname)); +extern const char * _nl_expand_alias PARAMS ((const char *name)); + +/* Locale name. */ +static const char *locale; + +/* Language (ISO-639 code) and optional territory (ISO-3166 code). */ +static const char *catalogname; + +/* Language (ISO-639 code). */ +static const char *language; + +/* Verbosity control. */ +static bool verbose; + +/* Long options. */ +static const struct option long_options[] = +{ + { "help", no_argument, NULL, 'h' }, + { "input", required_argument, NULL, 'i' }, + { "locale", required_argument, NULL, 'l' }, + { "output-file", required_argument, NULL, 'o' }, + { "verbose", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, + { "width", required_argument, NULL, 'w' }, + { NULL, 0, NULL, 0 } +}; + +/* Prototypes for local functions. Needed to ensure compiler checking of + function argument counts despite of K&R C function definition syntax. */ +static void usage PARAMS ((int status)) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; +static const char *find_pot PARAMS ((void)); +static const char *catalogname_for_locale PARAMS ((const char *locale)); +static const char *language_of_locale PARAMS ((const char *locale)); +static const char *canonical_locale_charset PARAMS ((void)); +static const char *englishname_of_language PARAMS ((void)); +static const char *project_id PARAMS ((void)); +static const char *project_id_version PARAMS ((void)); +static const char *po_revision_date PARAMS ((void)); +static struct passwd *get_user_pwd PARAMS ((void)); +static const char *get_user_fullname PARAMS ((void)); +static const char *get_user_email PARAMS ((void)); +static const char *last_translator PARAMS ((void)); +static const char *language_team_address PARAMS ((void)); +static const char *language_team PARAMS ((void)); +static const char *mime_version PARAMS ((void)); +static const char *content_type PARAMS ((void)); +static const char *content_transfer_encoding PARAMS ((void)); +static const char *plural_forms PARAMS ((void)); +#ifdef unused +static char *get_field PARAMS ((const char *header, const char *field)); +#endif +static char *put_field PARAMS ((const char *old_header, const char *field, + const char *value)); +static const char *get_title PARAMS ((void)); +static const char *subst_string PARAMS ((const char *str, + unsigned int nsubst, + const char *(*subst)[2])); +static void subst_string_list PARAMS ((string_list_ty *slp, + unsigned int nsubst, + const char *(*subst)[2])); +static msgdomain_list_ty *fill_header PARAMS ((msgdomain_list_ty *mdlp)); + + +int +main (argc, argv) + int argc; + char **argv; +{ + int opt; + bool do_help; + bool do_version; + char *output_file; + const char *input_file; + msgdomain_list_ty *result; + + /* Set program name for messages. */ + set_program_name (argv[0]); + error_print_progname = maybe_print_progname; + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + /* Set default values for variables. */ + do_help = false; + do_version = false; + output_file = NULL; + input_file = NULL; + locale = NULL; + + while ((opt = getopt_long (argc, argv, "hi:l:o:vVw:", long_options, NULL)) + != EOF) + switch (opt) + { + case '\0': /* Long option. */ + break; + + case 'h': + do_help = true; + break; + + case 'i': + if (input_file != NULL) + { + error (EXIT_SUCCESS, 0, _("at most one input file allowed")); + usage (EXIT_FAILURE); + } + input_file = optarg; + break; + + case 'l': + locale = optarg; + break; + + case 'o': + output_file = optarg; + break; + + case 'v': + verbose = true; + break; + + case 'V': + do_version = true; + break; + + case 'w': + { + int value; + char *endp; + value = strtol (optarg, &endp, 10); + if (endp != optarg) + message_page_width_set (value); + } + break; + + default: + usage (EXIT_FAILURE); + break; + } + + /* Version information is requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +This is free software; see the source for copying conditions. There is NO\n\ +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ +"), + "2001"); + printf (_("Written by %s.\n"), "Bruno Haible"); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + /* Test for extraneous arguments. */ + if (optind != argc) + error (EXIT_FAILURE, 0, _("too many arguments")); + + /* Search for the input file. */ + if (input_file == NULL) + input_file = find_pot (); + + /* Determine target locale. */ + if (locale == NULL) + { + locale = _nl_locale_name (LC_MESSAGES, "LC_MESSAGES"); + if (strcmp (locale, "C") == 0) + { + multiline_error (xstrdup (""), + xstrdup (_("\ +You are in a language indifferent environment. Please set\n\ +your LANG environment variable, as described in the ABOUT-NLS\n\ +file. This is necessary so you can test your translations.\n"))); + exit (EXIT_FAILURE); + } + } + { + const char *alias = _nl_expand_alias (locale); + if (alias != NULL) + locale = alias; + } + catalogname = catalogname_for_locale (locale); + language = language_of_locale (locale); + + /* Default output file name is CATALOGNAME.po. */ + if (output_file == NULL) + { + size_t cnlen = strlen (catalogname); + + output_file = (char *) xmalloc (cnlen + 3 + 1); + memcpy (output_file, catalogname, cnlen); + memcpy (output_file + cnlen, ".po", 3 + 1); + + /* But don't overwrite existing PO files. */ + if (access (output_file, F_OK) == 0) + { + multiline_error (xstrdup (""), + xasprintf (_("\ +Output file %s already exists.\n\ +Please specify the locale through the --locale option or\n\ +the output .po file through the --output-file option.\n"), + output_file)); + exit (EXIT_FAILURE); + } + } + + /* Read input file. */ + result = read_po_file (input_file); + + /* Fill the header entry. */ + result = fill_header (result); + + /* Initialize translations. */ + if (strcmp (language, "en") == 0) + result = msgdomain_list_english (result); + + /* Write the modified message list out. */ + msgdomain_list_print (result, output_file, true, false); + + if (verbose) + fprintf (stderr, "Created %s.\n", output_file); + + exit (EXIT_SUCCESS); +} + + +/* Display usage information and exit. */ +static void +usage (status) + int status; +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + /* xgettext: no-wrap */ + printf (_("\ +Usage: %s [OPTION]\n\ +"), program_name); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Creates a new PO file, initializing the meta information with values from the\n\ +user's environment.\n\ +")); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +")); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Input file location:\n\ + -i, --input=INPUTFILE input POT file\n\ +If no input file is given, the current directory is searched for the POT file.\n\ +If it is -, standard input is read.\n\ +")); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Output file location:\n\ + -o, --output-file=FILE write output to specified PO file\n\ +If no output file is given, it depends on the --locale option or the user's\n\ +locale setting. If it is -, the results are written to standard output.\n\ +")); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Output details:\n\ + -l, --locale=LL_CC set target locale\n\ + -w, --width=NUMBER set output page width\n\ +")); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Informative output:\n\ + -h, --help display this help and exit\n\ + -V, --version output version information and exit\n\ + -v, --verbose increase verbosity level\n\ +")); + printf ("\n"); + fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), + stdout); + } + + exit (status); +} + + +/* Search for the POT file and return its name. */ +static const char * +find_pot () +{ +#if HAVE_DIR + DIR *dirp; + char *found = NULL; + + dirp = opendir ("."); + if (dirp != NULL) + { + for (;;) + { + struct dirent *dp; + + errno = 0; + dp = readdir (dirp); + if (dp != NULL) + { + const char *name = dp->d_name; + size_t namlen = strlen (name); + + if (namlen > 4 && memcmp (name + namlen - 4, ".pot", 4) == 0) + { + if (found == NULL) + found = xstrdup (name); + else + { + multiline_error (xstrdup (""), + xstrdup (_("\ +Found more than one .pot file.\n\ +Please specify the input .pot file through the --input option.\n"))); + usage (EXIT_FAILURE); + } + } + } + else if (errno != 0) + error (EXIT_FAILURE, errno, _("error reading current directory")); + else + break; + } + if (CLOSEDIR (dirp)) + error (EXIT_FAILURE, errno, _("error reading current directory")); + + if (found != NULL) + return found; + } +#endif + + multiline_error (xstrdup (""), + xstrdup (_("\ +Found no .pot file in the current directory.\n\ +Please specify the input .pot file through the --input option.\n"))); + usage (EXIT_FAILURE); +} + + +/* Return the gettext catalog name corresponding to a locale. If the locale + consists of a language and a territory, and the language is mainly spoken + in that territory, the territory is removed from the locale name. + For example, "de_DE" or "de_DE.ISO-8859-1" are simplified to "de", + because the resulting catalog can be used as a default for all "de_XX", + such as "de_AT". */ +static const char * +catalogname_for_locale (locale) + const char *locale; +{ + static const char *locales_with_principal_territory[] = { + /* Language Main territory */ + "af_ZA", /* Afrikaans South Africa */ + "am_ET", /* Amharic Ethiopia */ + "as_IN", /* Assamese India */ + "az_AZ", /* Azerbaijani Azerbaijan */ + "be_BY", /* Belarusian Belarus */ + "bg_BG", /* Bulgarian Bulgaria */ + "bn_IN", /* Bengali India */ + "bo_CN", /* Tibetan China */ + "br_FR", /* Breton France */ + "bs_BA", /* Bosnian Bosnia */ + "ca_ES", /* Catalan Spain */ + "ce_RU", /* Chechen Russia */ + "co_FR", /* Corsican France */ + "cs_CZ", /* Czech Czech Republic */ + "cy_GB", /* Welsh Britain */ + "da_DK", /* Danish Denmark */ + "de_DE", /* German Germany */ + "dz_BT", /* Dzongkha Bhutan */ + "el_GR", /* Greek Greece */ + /* Don't put "en_GB" or "en_US" here. That would be asking for fruitless + political discussion. */ + "es_ES", /* Spanish Spain */ + "et_EE", /* Estonian Estonia */ + "fa_IR", /* Persian Iran */ + "fi_FI", /* Finnish Finland */ + "fj_FJ", /* Fijian Fiji */ + "fo_FO", /* Faroese Faeroe Islands */ + "fr_FR", /* French France */ + "ga_IE", /* Irish Ireland */ + "gd_GB", /* Scots Britain */ + "gu_IN", /* Gujarati India */ + "he_IL", /* Hebrew Israel */ + "hi_IN", /* Hindi India */ + "hr_HR", /* Croatian Croatia */ + "hu_HU", /* Hungarian Hungary */ + "hy_AM", /* Armenian Armenia */ + "id_ID", /* Indonesian Indonesia */ + "is_IS", /* Icelandic Iceland */ + "it_IT", /* Italian Italy */ + "ja_JP", /* Japanese Japan */ + "jw_ID", /* Javanese Indonesia */ + "ka_GE", /* Georgian Georgia */ + "kk_KZ", /* Kazakh Kazakhstan */ + "kl_GL", /* Kalaallisut Greenland */ + "km_KH", /* Khmer Cambodia */ + "kn_IN", /* Kannada India */ + "ko_KR", /* Korean Korea (South) */ + "kok_IN", /* Konkani India */ + "lo_LA", /* Laotian Laos */ + "lt_LT", /* Lithuanian Lithuania */ + "lv_LV", /* Latvian Latvia */ + "mg_MG", /* Malagasy Madagascar */ + "mk_MK", /* Macedonian Macedonia */ + "ml_IN", /* Malayalam India */ + "mn_MN", /* Mongolian Mongolia */ + "mr_IN", /* Marathi India */ + "ms_MY", /* Malay Malaysia */ + "mt_MT", /* Maltese Malta */ + "my_MM", /* Burmese Myanmar */ + "mni_IN", /* Manipuri India */ + "na_NR", /* Nauru Nauru */ + "nb_NO", /* Norwegian Bokmål Norway */ + "ne_NP", /* Nepali Nepal */ + "nl_NL", /* Dutch Netherlands */ + "nn_NO", /* Norwegian Nynorsk Norway */ + "no_NO", /* Norwegian Norway */ + "oc_FR", /* Occitan France */ + "or_IN", /* Oriya India */ + "pa_IN", /* Punjabi India */ + "pl_PL", /* Polish Poland */ + "ps_AF", /* Pashto Afghanistan */ + "pt_PT", /* Portuguese Portugal */ + "rm_CH", /* Rhaeto-Roman Switzerland */ + "rn_BI", /* Kirundi Burundi */ + "ro_RO", /* Romanian Romania */ + "ru_RU", /* Russian Russia */ + "sa_IN", /* Sanskrit India */ + "sc_IT", /* Sardinian Italy */ + "sg_CF", /* Sango Central African Rep. */ + "si_LK", /* Sinhalese Sri Lanka */ + "sk_SK", /* Slovak Slovakia */ + "sl_SI", /* Slovenian Slovenia */ + "so_SO", /* Somali Somalia */ + "sq_AL", /* Albanian Albania */ + "sr_YU", /* Serbian Yugoslavia */ + "sv_SE", /* Swedish Sweden */ + "te_IN", /* Telugu India */ + "tg_TJ", /* Tajik Tajikistan */ + "th_TH", /* Thai Thailand */ + "tk_TM", /* Turkmen Turkmenistan */ + "tl_PH", /* Tagalog Philippines */ + "to_TO", /* Tonga Tonga */ + "tr_TR", /* Turkish Turkey */ + "uk_UA", /* Ukrainian Ukraine */ + "ur_PK", /* Urdu Pakistan */ + "uz_UZ", /* Uzbek Uzbekistan */ + "vi_VN", /* Vietnamese Vietnam */ + "wen_DE" /* Sorbian Germany */ + }; + const char *dot; + size_t i; + + /* Remove the ".codeset" part from the locale. */ + dot = strchr (locale, '.'); + if (dot != NULL) + { + const char *codeset_end; + char *shorter_locale; + + codeset_end = strpbrk (dot + 1, "_@+,"); + if (codeset_end == NULL) + codeset_end = dot + strlen (dot); + + shorter_locale = (char *) xmalloc (strlen (locale)); + memcpy (shorter_locale, locale, dot - locale); + strcpy (shorter_locale + (dot - locale), codeset_end); + locale = shorter_locale; + } + + /* If the territory is the language's principal territory, drop it. */ + for (i = 0; i < SIZEOF (locales_with_principal_territory); i++) + if (strcmp (locale, locales_with_principal_territory[i]) == 0) + { + const char *language_end; + size_t len; + char *shorter_locale; + + language_end = strchr (locale, '_'); + if (language_end == NULL) + abort (); + + len = language_end - locale; + shorter_locale = (char *) xmalloc (len + 1); + memcpy (shorter_locale, locale, len); + shorter_locale[len] = '\0'; + locale = shorter_locale; + break; + } + + return locale; +} + + +/* Return the language of a locale. */ +static const char * +language_of_locale (locale) + const char *locale; +{ + const char *language_end; + + language_end = strpbrk (locale, "_.@+,"); + if (language_end != NULL) + { + size_t len; + char *result; + + len = language_end - locale; + result = (char *) xmalloc (len + 1); + memcpy (result, locale, len); + result[len] = '\0'; + + return result; + } + else + return locale; +} + + +/* Return the most likely desired charset for the PO file, as a portable + charset name. */ +static const char * +canonical_locale_charset () +{ + const char *charset; + + /* Get the current locale's charset and canonicalize it. */ + charset = locale_charset (); + charset = po_charset_canonicalize (charset); + if (charset == NULL) + charset = po_charset_ascii; + + return charset; +} + + +/* Return the English name of the language. */ +static const char * +englishname_of_language () +{ + /* Derived from ISO 639. */ + static struct { const char *code; const char *english; } table[] = { + { "aa", "Afar" }, + { "ab", "Abkhazian" }, + { "ae", "Avestan" }, + { "af", "Afrikaans" }, + { "am", "Amharic" }, + { "ar", "Arabic" }, + { "as", "Assamese" }, + { "ay", "Aymara" }, + { "az", "Azerbaijani" }, + { "ba", "Bashkir" }, + { "be", "Belarusian" }, + { "bg", "Bulgarian" }, + { "bh", "Bihari" }, + { "bi", "Bislama" }, + { "bn", "Bengali" }, + { "bo", "Tibetan" }, + { "br", "Breton" }, + { "bs", "Bosnian" }, + { "ca", "Catalan" }, + { "ce", "Chechen" }, + { "ch", "Chamorro" }, + { "co", "Corsican" }, + { "cs", "Czech" }, + { "cu", "Church Slavic" }, + { "cv", "Chuvash" }, + { "cy", "Welsh" }, + { "da", "Danish" }, + { "de", "German" }, + { "dz", "Dzongkha" }, + { "el", "Greek" }, + { "en", "English" }, + { "eo", "Esperanto" }, + { "es", "Spanish" }, + { "et", "Estonian" }, + { "eu", "Basque" }, + { "fa", "Persian" }, + { "fi", "Finnish" }, + { "fj", "Fijian" }, + { "fo", "Faroese" }, + { "fr", "French" }, + { "fy", "Frisian" }, + { "ga", "Irish" }, + { "gd", "Scots" }, + { "gl", "Galician" }, + { "gn", "Guarani" }, + { "gu", "Gujarati" }, + { "gv", "Manx" }, + { "ha", "Hausa" }, + { "he", "Hebrew" }, + { "hi", "Hindi" }, + { "ho", "Hiri Motu" }, + { "hr", "Croatian" }, + { "hu", "Hungarian" }, + { "hy", "Armenian" }, + { "hz", "Herero" }, + { "ia", "Interlingua" }, + { "id", "Indonesian" }, + { "ie", "Interlingue" }, + { "ik", "Inupiak" }, + { "is", "Icelandic" }, + { "it", "Italian" }, + { "iu", "Inuktitut" }, + { "ja", "Japanese" }, + { "jw", "Javanese" }, + { "ka", "Georgian" }, + { "ki", "Kikuyu" }, + { "kj", "Kuanyama" }, + { "kk", "Kazakh" }, + { "kl", "Kalaallisut" }, + { "km", "Khmer" }, + { "kn", "Kannada" }, + { "ko", "Korean" }, + { "ks", "Kashmiri" }, + { "ku", "Kurdish" }, + { "kv", "Komi" }, + { "kw", "Cornish" }, + { "ky", "Kirghiz" }, + { "kok", "Konkani" }, + { "la", "Latin" }, + { "lb", "Letzeburgesch" }, + { "ln", "Lingala" }, + { "lo", "Laotian" }, + { "lt", "Lithuanian" }, + { "lv", "Latvian" }, + { "mg", "Malagasy" }, + { "mh", "Marshall" }, + { "mi", "Maori" }, + { "mk", "Macedonian" }, + { "ml", "Malayalam" }, + { "mn", "Mongolian" }, + { "mo", "Moldavian" }, + { "mr", "Marathi" }, + { "ms", "Malay" }, + { "mt", "Maltese" }, + { "my", "Burmese" }, + { "mni", "Manipuri" }, + { "na", "Nauru" }, + { "nb", "Norwegian Bokmal" }, + { "nd", "North Ndebele" }, + { "ne", "Nepali" }, + { "ng", "Ndonga" }, + { "nl", "Dutch" }, + { "nn", "Norwegian Nynorsk" }, + { "no", "Norwegian" }, + { "nr", "South Ndebele" }, + { "nv", "Navajo" }, + { "ny", "Nyanja" }, + { "oc", "Occitan" }, + { "om", "(Afan) Oromo" }, + { "or", "Oriya" }, + { "os", "Ossetian" }, + { "pa", "Punjabi" }, + { "pi", "Pali" }, + { "pl", "Polish" }, + { "ps", "Pashto" }, + { "pt", "Portuguese" }, + { "qu", "Quechua" }, + { "rm", "Rhaeto-Roman" }, + { "rn", "Kirundi" }, + { "ro", "Romanian" }, + { "ru", "Russian" }, + { "rw", "Kinyarwanda" }, + { "sa", "Sanskrit" }, + { "sc", "Sardinian" }, + { "sd", "Sindhi" }, + { "se", "Northern Sami" }, + { "sg", "Sango" }, + { "si", "Sinhalese" }, + { "sk", "Slovak" }, + { "sl", "Slovenian" }, + { "sm", "Samoan" }, + { "sn", "Shona" }, + { "so", "Somali" }, + { "sq", "Albanian" }, + { "sr", "Serbian" }, + { "ss", "Siswati" }, + { "st", "Sesotho" }, + { "su", "Sundanese" }, + { "sv", "Swedish" }, + { "sw", "Swahili" }, + { "ta", "Tamil" }, + { "te", "Telugu" }, + { "tg", "Tajik" }, + { "th", "Thai" }, + { "ti", "Tigrinya" }, + { "tk", "Turkmen" }, + { "tl", "Tagalog" }, + { "tn", "Setswana" }, + { "to", "Tonga" }, + { "tr", "Turkish" }, + { "ts", "Tsonga" }, + { "tt", "Tatar" }, + { "tw", "Twi" }, + { "ty", "Tahitian" }, + { "ug", "Uighur" }, + { "uk", "Ukrainian" }, + { "ur", "Urdu" }, + { "uz", "Uzbek" }, + { "vi", "Vietnamese" }, + { "vo", "Volapuk" }, + { "wo", "Wolof" }, + { "wen", "Sorbian" }, + { "xh", "Xhosa" }, + { "yi", "Yiddish" }, + { "yo", "Yoruba" }, + { "za", "Zhuang" }, + { "zh", "Chinese" }, + { "zu", "Zulu" } + }; + size_t i; + + for (i = 0; i < SIZEOF (table); i ++) + if (strcmp (table[i].code, language) == 0) + return table[i].english; + + return xasprintf ("Language %s", language); +} + + +/* Construct the value for the PACKAGE name. */ +static const char * +project_id () +{ + char *prog = concatenated_pathname (LIBDIR, "gettext/project-id", NULL); + char *argv[3]; + pid_t child; + int fd[1]; + FILE *fp; + char *line; + size_t linesize; + size_t linelen; + int exitstatus; + + /* Call the project-id shell script. */ + argv[0] = "/bin/sh"; + argv[1] = prog; + argv[2] = NULL; + child = create_pipe_in (prog, "/bin/sh", argv, "/dev/null", false, true, fd); + + /* Retrieve its result. */ + fp = fdopen (fd[0], "r"); + if (fp == NULL) + error (EXIT_FAILURE, errno, _("fdopen() failed")); + + line = NULL; linesize = 0; + linelen = getline (&line, &linesize, fp); + if (linelen == (size_t)(-1)) + error (EXIT_FAILURE, 0, _("%s subprocess I/O error"), prog); + if (linelen > 0 && line[linelen - 1] == '\n') + line[linelen - 1] = '\0'; + + fclose (fp); + + /* Remove zombie process from process list, and retrieve exit status. */ + exitstatus = wait_subprocess (child, prog, true); + if (exitstatus != 0) + error (EXIT_FAILURE, 0, _("%s subprocess failed with exit code %d"), + prog, exitstatus); + + return line; +} + + +/* Construct the value for the Project-Id-Version field. */ +static const char * +project_id_version () +{ + char *prog = concatenated_pathname (LIBDIR, "gettext/project-id", NULL); + char *argv[4]; + pid_t child; + int fd[1]; + FILE *fp; + char *line; + size_t linesize; + size_t linelen; + int exitstatus; + + /* Call the project-id shell script. */ + argv[0] = "/bin/sh"; + argv[1] = prog; + argv[2] = "yes"; + argv[3] = NULL; + child = create_pipe_in (prog, "/bin/sh", argv, "/dev/null", false, true, fd); + + /* Retrieve its result. */ + fp = fdopen (fd[0], "r"); + if (fp == NULL) + error (EXIT_FAILURE, errno, _("fdopen() failed")); + + line = NULL; linesize = 0; + linelen = getline (&line, &linesize, fp); + if (linelen == (size_t)(-1)) + error (EXIT_FAILURE, 0, _("%s subprocess I/O error"), prog); + if (linelen > 0 && line[linelen - 1] == '\n') + line[linelen - 1] = '\0'; + + fclose (fp); + + /* Remove zombie process from process list, and retrieve exit status. */ + exitstatus = wait_subprocess (child, prog, true); + if (exitstatus != 0) + error (EXIT_FAILURE, 0, _("%s subprocess failed with exit code %d"), + prog, exitstatus); + + return line; +} + + +/* Construct the value for the PO-Revision-Date field. */ +static const char * +po_revision_date () +{ + time_t now; + + time (&now); + return po_strftime (&now); +} + + +/* Returns the struct passwd entry for the current user. */ +static struct passwd * +get_user_pwd () +{ + const char *username; + struct passwd *userpasswd; + + /* 1. attempt: getpwnam(getenv("USER")) */ + username = getenv ("USER"); + if (username != NULL) + { + errno = 0; + userpasswd = getpwnam (username); + if (userpasswd != NULL) + return userpasswd; + if (errno != 0) + error (EXIT_FAILURE, errno, "getpwnam(\"%s\")", username); + } + + /* 2. attempt: getpwnam(getlogin()) */ + username = getlogin (); + if (username != NULL) + { + errno = 0; + userpasswd = getpwnam (username); + if (userpasswd != NULL) + return userpasswd; + if (errno != 0) + error (EXIT_FAILURE, errno, "getpwnam(\"%s\")", username); + } + + /* 3. attempt: getpwuid(getuid()) */ + errno = 0; + userpasswd = getpwuid (getuid ()); + if (userpasswd != NULL) + return userpasswd; + if (errno != 0) + error (EXIT_FAILURE, errno, "getpwuid(\"%d\")", getuid ()); + + return NULL; +} + + +/* Return the user's full name. */ +static const char * +get_user_fullname () +{ + struct passwd *pwd; + const char *fullname; + const char *fullname_end; + char *result; + + pwd = get_user_pwd (); + if (pwd == NULL) + return NULL; + + /* Return the pw_gecos field, upto the first comma (if any). */ + fullname = pwd->pw_gecos; + fullname_end = strchr (fullname, ','); + if (fullname_end == NULL) + fullname_end = fullname + strlen (fullname); + + result = (char *) xmalloc (fullname_end - fullname + 1); + memcpy (result, fullname, fullname_end - fullname); + result[fullname_end - fullname] = '\0'; + + return result; +} + + +/* Return the user's email address. */ +static const char * +get_user_email () +{ + char *prog = concatenated_pathname (LIBDIR, "gettext/user-email", NULL); + char *argv[4]; + pid_t child; + int fd[1]; + FILE *fp; + char *line; + size_t linesize; + size_t linelen; + int exitstatus; + + /* Ask the user for his email address. */ + argv[0] = "/bin/sh"; + argv[1] = prog; + argv[2] = _("\ +The new message catalog should contain your email address, so that users can\n\ +give you feedback about the translations, and so that maintainers can contact\n\ +you in case of unexpected technical problems.\n"); + argv[3] = NULL; + child = create_pipe_in (prog, "/bin/sh", argv, "/dev/null", false, true, fd); + + /* Retrieve his answer. */ + fp = fdopen (fd[0], "r"); + if (fp == NULL) + error (EXIT_FAILURE, errno, _("fdopen() failed")); + + line = NULL; linesize = 0; + linelen = getline (&line, &linesize, fp); + if (linelen == (size_t)(-1)) + error (EXIT_FAILURE, 0, _("%s subprocess I/O error"), prog); + if (linelen > 0 && line[linelen - 1] == '\n') + line[linelen - 1] = '\0'; + + fclose (fp); + + /* Remove zombie process from process list, and retrieve exit status. */ + exitstatus = wait_subprocess (child, prog, true); + if (exitstatus != 0) + error (EXIT_FAILURE, 0, _("%s subprocess failed with exit code %d"), + prog, exitstatus); + + return line; +} + + +/* Construct the value for the Last-Translator field. */ +static const char * +last_translator () +{ + const char *fullname = get_user_fullname (); + const char *email = get_user_email (); + + if (fullname != NULL) + return xasprintf ("%s <%s>", fullname, email); + else + return xasprintf ("<%s>", email); +} + + +/* Return the language team's mailing list address or homepage URL. */ +static const char * +language_team_address () +{ + char *prog = concatenated_pathname (PROJECTSDIR, "team-address", NULL); + char *argv[8]; + pid_t child; + int fd[1]; + FILE *fp; + char *line; + size_t linesize; + size_t linelen; + int exitstatus; + + /* Call the team-address shell script. */ + argv[0] = "/bin/sh"; + argv[1] = prog; + argv[2] = PROJECTSDIR; + argv[3] = concatenated_pathname (LIBDIR, "gettext", NULL); + argv[4] = (char *) catalogname; + argv[5] = (char *) language; + argv[6] = (verbose ? "yes" : ""); + argv[7] = NULL; + child = create_pipe_in (prog, "/bin/sh", argv, "/dev/null", false, true, fd); + + /* Retrieve its result. */ + fp = fdopen (fd[0], "r"); + if (fp == NULL) + error (EXIT_FAILURE, errno, _("fdopen() failed")); + + line = NULL; linesize = 0; + linelen = getline (&line, &linesize, fp); + if (linelen == (size_t)(-1)) + line = ""; + else if (linelen > 0 && line[linelen - 1] == '\n') + line[linelen - 1] = '\0'; + + fclose (fp); + + /* Remove zombie process from process list, and retrieve exit status. */ + exitstatus = wait_subprocess (child, prog, true); + if (exitstatus != 0) + error (EXIT_FAILURE, 0, _("%s subprocess failed with exit code %d"), + prog, exitstatus); + + return line; +} + + +/* Construct the value for the Language-Team field. */ +static const char * +language_team () +{ + const char *englishname = englishname_of_language (); + const char *address = language_team_address (); + + if (address != NULL && address[0] != '\0') + return xasprintf ("%s %s", englishname, address); + else + return englishname; +} + + +/* Construct the value for the MIME-Version field. */ +static const char * +mime_version () +{ + return "1.0"; +} + + +/* Construct the value for the Content-Type field. */ +static const char * +content_type () +{ + return xasprintf ("text/plain; charset=%s", canonical_locale_charset ()); +} + + +/* Construct the value for the Content-Transfer-Encoding field. */ +static const char * +content_transfer_encoding () +{ + return "8bit"; +} + + +/* Construct the value for the Plural-Forms field. */ +static const char * +plural_forms () +{ + /* Formulas taken from the documentation, node "Plural forms". */ + static struct { const char *lang; const char *value; } table[] = + { + { "hu", "nplurals=1; plural=0;" }, + { "ja", "nplurals=1; plural=0;" }, + { "ko", "nplurals=1; plural=0;" }, + { "tr", "nplurals=1; plural=0;" }, + { "da", "nplurals=2; plural=(n != 1);" }, + { "nl", "nplurals=2; plural=(n != 1);" }, + { "en", "nplurals=2; plural=(n != 1);" }, + { "de", "nplurals=2; plural=(n != 1);" }, + { "nb", "nplurals=2; plural=(n != 1);" }, + { "no", "nplurals=2; plural=(n != 1);" }, + { "nn", "nplurals=2; plural=(n != 1);" }, + { "sv", "nplurals=2; plural=(n != 1);" }, + { "et", "nplurals=2; plural=(n != 1);" }, + { "fi", "nplurals=2; plural=(n != 1);" }, + { "el", "nplurals=2; plural=(n != 1);" }, + { "he", "nplurals=2; plural=(n != 1);" }, + { "it", "nplurals=2; plural=(n != 1);" }, + { "pt", "nplurals=2; plural=(n != 1);" }, + { "es", "nplurals=2; plural=(n != 1);" }, + { "eo", "nplurals=2; plural=(n != 1);" }, + { "fr", "nplurals=2; plural=(n > 1);" }, + { "pt_BR", "nplurals=2; plural=(n > 1);" }, + { "lv", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);" }, + { "ga", "nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2;" }, + { "lt", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);" }, + { "hr", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, + { "cs", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, + { "ru", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, + { "sk", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, + { "uk", "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, + { "pl", "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);" }, + { "sl", "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);" } + }; + size_t i; + + /* Search for a formula depending on the catalogname. */ + for (i = 0; i < SIZEOF (table); i++) + if (strcmp (table[i].lang, catalogname) == 0) + return table[i].value; + + /* Search for a formula depending on the language only. */ + for (i = 0; i < SIZEOF (table); i++) + if (strcmp (table[i].lang, language) == 0) + return table[i].value; + + return NULL; +} + + +static struct { const char *name; const char * (*getter) PARAMS ((void)); } +fields[] = + { + { "Project-Id-Version", project_id_version }, + { "PO-Revision-Date", po_revision_date }, + { "Last-Translator", last_translator }, + { "Language-Team", language_team }, + { "MIME-Version", mime_version }, + { "Content-Type", content_type }, + { "Content-Transfer-Encoding", content_transfer_encoding }, + { "Plural-Forms", plural_forms } + }; + +#define NFIELDS SIZEOF (fields) +#define FIELD_LAST_TRANSLATOR 2 + + +#ifdef unused +/* Retrieve a freshly allocated copy of a field's value. */ +static char * +get_field (header, field) + const char *header; + const char *field; +{ + size_t len = strlen (field); + const char *line; + + for (line = header;;) + { + if (strncmp (line, field, len) == 0 + && line[len] == ':' && line[len + 1] == ' ') + { + const char *value_start; + const char *value_end; + char *value; + + value_start = line + len + 2; + value_end = strchr (value_start, '\n'); + if (value_end == NULL) + value_end = value_start + strlen (value_start); + + value = (char *) xmalloc (value_end - value_start + 1); + memcpy (value, value_start, value_end - value_start); + value[value_end - value_start] = '\0'; + + return value; + } + + line = strchr (line, '\n'); + if (line != NULL) + line++; + else + break; + } + + return NULL; +} +#endif + +/* Add a field with value to a header, and return the new header. */ +static char * +put_field (old_header, field, value) + const char *old_header; + const char *field; + const char *value; +{ + size_t len = strlen (field); + const char *line; + char *new_header; + char *p; + + for (line = old_header;;) + { + if (strncmp (line, field, len) == 0 + && line[len] == ':' && line[len + 1] == ' ') + { + const char *value_start; + const char *value_end; + + value_start = line + len + 2; + value_end = strchr (value_start, '\n'); + if (value_end == NULL) + value_end = value_start + strlen (value_start); + + new_header = (char *) xmalloc (strlen (old_header) + - (value_end - value_start) + + strlen (value) + + (*value_end != '\n' ? 1 : 0) + + 1); + p = new_header; + memcpy (p, old_header, value_start - old_header); + p += value_start - old_header; + memcpy (p, value, strlen (value)); + p += strlen (value); + if (*value_end != '\n') + *p++ = '\n'; + strcpy (p, value_end); + + return new_header; + } + + line = strchr (line, '\n'); + if (line != NULL) + line++; + else + break; + } + + new_header = (char *) xmalloc (strlen (old_header) + 1 + + len + 2 + strlen (value) + 1 + + 1); + p = new_header; + memcpy (p, old_header, strlen (old_header)); + p += strlen (old_header); + if (p > new_header && p[-1] != '\n') + *p++ = '\n'; + memcpy (p, field, len); + p += len; + *p++ = ':'; + *p++ = ' '; + memcpy (p, value, strlen (value)); + p += strlen (value); + *p++ = '\n'; + *p = '\0'; + + return new_header; +} + + +/* Return the title format string. */ +static const char * +get_title () +{ + /* This is tricky. We want the translation in the given locale specified by + the command line, not the current locale. But we want it in the encoding + that we put into the header entry, not the encoding of that locale. + We could avoid the use of OUTPUT_CHARSET by using a separate message + catalog and bind_textdomain_codeset(), but that doesn't seem worth the + trouble for one single message. */ + const char *encoding; + const char *tmp; + char *old_LC_ALL; + char *old_LANGUAGE; + char *old_OUTPUT_CHARSET; + const char *msgid; + const char *result; + + encoding = canonical_locale_charset (); + + /* Save LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables. */ + + tmp = getenv ("LC_ALL"); + old_LC_ALL = (tmp != NULL ? xstrdup (tmp) : NULL); + + tmp = getenv ("LANGUAGE"); + old_LANGUAGE = (tmp != NULL ? xstrdup (tmp) : NULL); + + tmp = getenv ("OUTPUT_CHARSET"); + old_OUTPUT_CHARSET = (tmp != NULL ? xstrdup (tmp) : NULL); + + setenv ("LC_ALL", locale, 1); + unsetenv ("LANGUAGE"); + setenv ("OUTPUT_CHARSET", encoding, 1); + +#ifdef HAVE_SETLOCALE + setlocale (LC_ALL, ""); +#endif + + /* Fetch the translation. */ + /* TRANSLATORS: "English" needs to be replaced by your language. + For example in it.po write "Traduzioni italiani ...", + *not* "Traduzioni inglesi ...". */ + msgid = N_("English translations for %s package"); + result = gettext (msgid); + if (result == msgid) + /* No translation found. */ + result = xasprintf ("%s translations for %%s package", + englishname_of_language ()); + + /* Restore LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables. */ + + if (old_LC_ALL != NULL) + setenv ("LC_ALL", old_LC_ALL, 1), free (old_LC_ALL); + else + unsetenv ("LC_ALL"); + + if (old_LANGUAGE != NULL) + setenv ("LANGUAGE", old_LANGUAGE, 1), free (old_LANGUAGE); + else + unsetenv ("LANGUAGE"); + + if (old_OUTPUT_CHARSET != NULL) + setenv ("OUTPUT_CHARSET", old_OUTPUT_CHARSET, 1), free (old_OUTPUT_CHARSET); + else + unsetenv ("OUTPUT_CHARSET"); + +#ifdef HAVE_SETLOCALE + setlocale (LC_ALL, ""); +#endif + + return result; +} + + +/* Perform a set of substitutions in a string and return the resulting + string. When subst[j][0] found, it is replaced with subst[j][1]. + subst[j][0] must not be the empty string. */ +static const char * +subst_string (str, nsubst, subst) + const char *str; + unsigned int nsubst; + const char *(*subst)[2]; +{ + if (nsubst > 0) + { + char *malloced = NULL; + size_t *substlen; + size_t i; + unsigned int j; + + substlen = (size_t *) alloca (nsubst * sizeof (size_t)); + for (j = 0; j < nsubst; j++) + { + substlen[j] = strlen (subst[j][0]); + if (substlen[j] == 0) + abort (); + } + + for (i = 0;;) + { + if (str[i] == '\0') + break; + for (j = 0; j < nsubst; j++) + if (*(str + i) == *subst[j][0] + && strncmp (str + i, subst[j][0], substlen[j]) == 0) + { + size_t replacement_len = strlen (subst[j][1]); + size_t new_len = strlen (str) - substlen[j] + replacement_len; + char *new_str = (char *) xmalloc (new_len + 1); + memcpy (new_str, str, i); + memcpy (new_str + i, subst[j][1], replacement_len); + strcpy (new_str + i + replacement_len, str + i + substlen[j]); + if (malloced != NULL) + free (malloced); + str = new_str; + malloced = new_str; + i += replacement_len; + break; + } + if (j == nsubst) + i++; + } + } + + return str; +} + +/* Perform a set of substitutions on each string of a string list. + When subst[j][0] found, it is replaced with subst[j][1]. subst[j][0] + must not be the empty string. */ +static void +subst_string_list (slp, nsubst, subst) + string_list_ty *slp; + unsigned int nsubst; + const char *(*subst)[2]; +{ + size_t j; + + for (j = 0; j < slp->nitems; j++) + slp->item[j] = subst_string (slp->item[j], nsubst, subst); +} + + +/* Fill the templates in all fields of the header entry. */ +static msgdomain_list_ty * +fill_header (mdlp) + msgdomain_list_ty *mdlp; +{ + /* Cache the strings filled in, for use when there are multiple domains + and a header entry for each domain. */ + const char *field_value[NFIELDS]; + size_t k, j, i; + + for (i = 0; i < NFIELDS; i++) + field_value[i] = NULL; + + for (k = 0; k < mdlp->nitems; k++) + { + message_list_ty *mlp = mdlp->item[k]->messages; + + if (mlp->nitems > 0) + { + message_ty *header_mp = NULL; + char *header; + const char *subst[3][2]; + time_t now; + + /* Search the header entry. */ + for (j = 0; j < mlp->nitems; j++) + if (mlp->item[j]->msgid[0] == '\0' && !mlp->item[j]->obsolete) + { + header_mp = mlp->item[j]; + break; + } + + /* If it wasn't found, provide one. */ + if (header_mp == NULL) + { + static lex_pos_ty pos = { __FILE__, __LINE__ }; + + header_mp = message_alloc ("", NULL, "", 1, &pos); + message_list_prepend (mlp, header_mp); + } + + header = xstrdup (header_mp->msgstr); + + /* Fill in the fields. */ + for (i = 0; i < NFIELDS; i++) + { + if (field_value[i] == NULL) + field_value[i] = fields[i].getter (); + + if (field_value[i] != NULL) + { + char *old_header = header; + header = put_field (header, fields[i].name, field_value[i]); + free (old_header); + } + } + + /* Replace the old translation in the header entry. */ + header_mp->msgstr = header; + header_mp->msgstr_len = strlen (header) + 1; + + /* Update the comments in the header entry. */ + subst[0][0] = "SOME DESCRIPTIVE TITLE"; + subst[0][1] = xasprintf (get_title (), project_id ()); + subst[1][0] = "FIRST AUTHOR <EMAIL@ADDRESS>"; + subst[1][1] = field_value[FIELD_LAST_TRANSLATOR]; + subst[2][0] = "YEAR"; + subst[2][1] = + xasprintf ("%d", (time (&now), (localtime (&now))->tm_year + 1900)); + subst_string_list (header_mp->comment, SIZEOF (subst), subst); + + /* Finally remove the fuzzy attribute. */ + header_mp->is_fuzzy = false; + } + } + + return mdlp; +} diff --git a/src/project-id b/src/project-id new file mode 100755 index 0000000..2514a84 --- /dev/null +++ b/src/project-id @@ -0,0 +1,67 @@ +#!/bin/sh +# Prints a package's identification PACKAGE VERSION or PACKAGE. + +want_version="$1" +while true; do + if test -f configure; then + package=`grep '^PACKAGE=' configure | sed -e '1q' | sed -e 's/^PACKAGE=//'` + case "$package" in + *[\"\$\`\{\}]*) + # Some packages (gcal) retrieve the package name dynamically. + package= + ;; + esac + if test -n "$package"; then + is_gnu=`LC_ALL=C grep "GNU $package" * 2>/dev/null` + if test -n "$is_gnu"; then + package="GNU $package" + fi + if test -n "$want_version"; then + version=`grep '^VERSION=' configure | sed -e '1q' | sed -e 's/^VERSION=//'` + case "$version" in + *[\"\$\`\{\}]*) + # Some packages (gcal, gcc, clisp) retrieve the version dynamically. + version= + ;; + esac + if test -n "$version"; then + echo "$package $version" + else + echo "$package" + fi + else + echo "$package" + fi + exit 0 + fi + fi + dir=`basename \`pwd\`` + case "$dir" in + i18n) + # This directory name, used in GNU make, is not the top level directory. + ;; + *[A-Za-z]*[0-9]*) + package=`echo "$dir" | sed -e 's/^\([^0-9]*\)[0-9].*$/\1/' -e 's/[-_]$//'` + if test -n "$want_version"; then + version=`echo "$dir" | sed -e 's/^[^0-9]*\([0-9].*\)$/\1/'` + echo "$package $version" + else + echo "$package" + fi + exit 0 + ;; + esac + # Go to parent directory + last=`/bin/pwd` + cd .. + curr=`/bin/pwd` + if test "$last" = "$curr"; then + # Oops, didn't find the package name. + if test -n "$want_version"; then + echo "PACKAGE VERSION" + else + echo "PACKAGE" + fi + exit 0 + fi +done |