1*946379e7Schristos /* Concatenates several translation catalogs.
2*946379e7Schristos Copyright (C) 2001-2006 Free Software Foundation, Inc.
3*946379e7Schristos Written by Bruno Haible <haible@clisp.cons.org>, 2001.
4*946379e7Schristos
5*946379e7Schristos This program is free software; you can redistribute it and/or modify
6*946379e7Schristos it under the terms of the GNU General Public License as published by
7*946379e7Schristos the Free Software Foundation; either version 2, or (at your option)
8*946379e7Schristos any later version.
9*946379e7Schristos
10*946379e7Schristos This program is distributed in the hope that it will be useful,
11*946379e7Schristos but WITHOUT ANY WARRANTY; without even the implied warranty of
12*946379e7Schristos MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13*946379e7Schristos GNU General Public License for more details.
14*946379e7Schristos
15*946379e7Schristos You should have received a copy of the GNU General Public License
16*946379e7Schristos along with this program; if not, write to the Free Software Foundation,
17*946379e7Schristos Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
18*946379e7Schristos
19*946379e7Schristos
20*946379e7Schristos #ifdef HAVE_CONFIG_H
21*946379e7Schristos # include "config.h"
22*946379e7Schristos #endif
23*946379e7Schristos
24*946379e7Schristos #include <getopt.h>
25*946379e7Schristos #include <limits.h>
26*946379e7Schristos #include <stdio.h>
27*946379e7Schristos #include <stdlib.h>
28*946379e7Schristos #include <locale.h>
29*946379e7Schristos
30*946379e7Schristos #include "closeout.h"
31*946379e7Schristos #include "dir-list.h"
32*946379e7Schristos #include "str-list.h"
33*946379e7Schristos #include "file-list.h"
34*946379e7Schristos #include "error.h"
35*946379e7Schristos #include "error-progname.h"
36*946379e7Schristos #include "progname.h"
37*946379e7Schristos #include "relocatable.h"
38*946379e7Schristos #include "basename.h"
39*946379e7Schristos #include "message.h"
40*946379e7Schristos #include "read-catalog.h"
41*946379e7Schristos #include "read-po.h"
42*946379e7Schristos #include "read-properties.h"
43*946379e7Schristos #include "read-stringtable.h"
44*946379e7Schristos #include "write-catalog.h"
45*946379e7Schristos #include "write-po.h"
46*946379e7Schristos #include "write-properties.h"
47*946379e7Schristos #include "write-stringtable.h"
48*946379e7Schristos #include "msgl-cat.h"
49*946379e7Schristos #include "exit.h"
50*946379e7Schristos #include "propername.h"
51*946379e7Schristos #include "gettext.h"
52*946379e7Schristos
53*946379e7Schristos #define _(str) gettext (str)
54*946379e7Schristos
55*946379e7Schristos
56*946379e7Schristos /* Force output of PO file even if empty. */
57*946379e7Schristos static int force_po;
58*946379e7Schristos
59*946379e7Schristos /* Target encoding. */
60*946379e7Schristos static const char *to_code;
61*946379e7Schristos
62*946379e7Schristos /* Long options. */
63*946379e7Schristos static const struct option long_options[] =
64*946379e7Schristos {
65*946379e7Schristos { "add-location", no_argument, &line_comment, 1 },
66*946379e7Schristos { "directory", required_argument, NULL, 'D' },
67*946379e7Schristos { "escape", no_argument, NULL, 'E' },
68*946379e7Schristos { "files-from", required_argument, NULL, 'f' },
69*946379e7Schristos { "force-po", no_argument, &force_po, 1 },
70*946379e7Schristos { "help", no_argument, NULL, 'h' },
71*946379e7Schristos { "indent", no_argument, NULL, 'i' },
72*946379e7Schristos { "no-escape", no_argument, NULL, 'e' },
73*946379e7Schristos { "no-location", no_argument, &line_comment, 0 },
74*946379e7Schristos { "no-wrap", no_argument, NULL, CHAR_MAX + 2 },
75*946379e7Schristos { "output-file", required_argument, NULL, 'o' },
76*946379e7Schristos { "properties-input", no_argument, NULL, 'P' },
77*946379e7Schristos { "properties-output", no_argument, NULL, 'p' },
78*946379e7Schristos { "sort-by-file", no_argument, NULL, 'F' },
79*946379e7Schristos { "sort-output", no_argument, NULL, 's' },
80*946379e7Schristos { "strict", no_argument, NULL, 'S' },
81*946379e7Schristos { "stringtable-input", no_argument, NULL, CHAR_MAX + 3 },
82*946379e7Schristos { "stringtable-output", no_argument, NULL, CHAR_MAX + 4 },
83*946379e7Schristos { "to-code", required_argument, NULL, 't' },
84*946379e7Schristos { "unique", no_argument, NULL, 'u' },
85*946379e7Schristos { "use-first", no_argument, NULL, CHAR_MAX + 1 },
86*946379e7Schristos { "version", no_argument, NULL, 'V' },
87*946379e7Schristos { "width", required_argument, NULL, 'w', },
88*946379e7Schristos { "more-than", required_argument, NULL, '>', },
89*946379e7Schristos { "less-than", required_argument, NULL, '<', },
90*946379e7Schristos { NULL, 0, NULL, 0 }
91*946379e7Schristos };
92*946379e7Schristos
93*946379e7Schristos
94*946379e7Schristos /* Forward declaration of local functions. */
95*946379e7Schristos static void usage (int status)
96*946379e7Schristos #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
97*946379e7Schristos __attribute__ ((noreturn))
98*946379e7Schristos #endif
99*946379e7Schristos ;
100*946379e7Schristos
101*946379e7Schristos
102*946379e7Schristos int
main(int argc,char ** argv)103*946379e7Schristos main (int argc, char **argv)
104*946379e7Schristos {
105*946379e7Schristos int cnt;
106*946379e7Schristos int optchar;
107*946379e7Schristos bool do_help;
108*946379e7Schristos bool do_version;
109*946379e7Schristos char *output_file;
110*946379e7Schristos const char *files_from;
111*946379e7Schristos string_list_ty *file_list;
112*946379e7Schristos msgdomain_list_ty *result;
113*946379e7Schristos catalog_input_format_ty input_syntax = &input_format_po;
114*946379e7Schristos catalog_output_format_ty output_syntax = &output_format_po;
115*946379e7Schristos bool sort_by_msgid = false;
116*946379e7Schristos bool sort_by_filepos = false;
117*946379e7Schristos
118*946379e7Schristos /* Set program name for messages. */
119*946379e7Schristos set_program_name (argv[0]);
120*946379e7Schristos error_print_progname = maybe_print_progname;
121*946379e7Schristos
122*946379e7Schristos #ifdef HAVE_SETLOCALE
123*946379e7Schristos /* Set locale via LC_ALL. */
124*946379e7Schristos setlocale (LC_ALL, "");
125*946379e7Schristos #endif
126*946379e7Schristos
127*946379e7Schristos /* Set the text message domain. */
128*946379e7Schristos bindtextdomain (PACKAGE, relocate (LOCALEDIR));
129*946379e7Schristos bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
130*946379e7Schristos textdomain (PACKAGE);
131*946379e7Schristos
132*946379e7Schristos /* Ensure that write errors on stdout are detected. */
133*946379e7Schristos atexit (close_stdout);
134*946379e7Schristos
135*946379e7Schristos /* Set default values for variables. */
136*946379e7Schristos do_help = false;
137*946379e7Schristos do_version = false;
138*946379e7Schristos output_file = NULL;
139*946379e7Schristos files_from = NULL;
140*946379e7Schristos more_than = 0;
141*946379e7Schristos less_than = INT_MAX;
142*946379e7Schristos use_first = false;
143*946379e7Schristos
144*946379e7Schristos while ((optchar = getopt_long (argc, argv, "<:>:D:eEf:Fhino:pPst:uVw:",
145*946379e7Schristos long_options, NULL)) != EOF)
146*946379e7Schristos switch (optchar)
147*946379e7Schristos {
148*946379e7Schristos case '\0': /* Long option. */
149*946379e7Schristos break;
150*946379e7Schristos
151*946379e7Schristos case '>':
152*946379e7Schristos {
153*946379e7Schristos int value;
154*946379e7Schristos char *endp;
155*946379e7Schristos value = strtol (optarg, &endp, 10);
156*946379e7Schristos if (endp != optarg)
157*946379e7Schristos more_than = value;
158*946379e7Schristos }
159*946379e7Schristos break;
160*946379e7Schristos
161*946379e7Schristos case '<':
162*946379e7Schristos {
163*946379e7Schristos int value;
164*946379e7Schristos char *endp;
165*946379e7Schristos value = strtol (optarg, &endp, 10);
166*946379e7Schristos if (endp != optarg)
167*946379e7Schristos less_than = value;
168*946379e7Schristos }
169*946379e7Schristos break;
170*946379e7Schristos
171*946379e7Schristos case 'D':
172*946379e7Schristos dir_list_append (optarg);
173*946379e7Schristos break;
174*946379e7Schristos
175*946379e7Schristos case 'e':
176*946379e7Schristos message_print_style_escape (false);
177*946379e7Schristos break;
178*946379e7Schristos
179*946379e7Schristos case 'E':
180*946379e7Schristos message_print_style_escape (true);
181*946379e7Schristos break;
182*946379e7Schristos
183*946379e7Schristos case 'f':
184*946379e7Schristos files_from = optarg;
185*946379e7Schristos break;
186*946379e7Schristos
187*946379e7Schristos case 'F':
188*946379e7Schristos sort_by_filepos = true;
189*946379e7Schristos break;
190*946379e7Schristos
191*946379e7Schristos case 'h':
192*946379e7Schristos do_help = true;
193*946379e7Schristos break;
194*946379e7Schristos
195*946379e7Schristos case 'i':
196*946379e7Schristos message_print_style_indent ();
197*946379e7Schristos break;
198*946379e7Schristos
199*946379e7Schristos case 'n':
200*946379e7Schristos line_comment = 1;
201*946379e7Schristos break;
202*946379e7Schristos
203*946379e7Schristos case 'o':
204*946379e7Schristos output_file = optarg;
205*946379e7Schristos break;
206*946379e7Schristos
207*946379e7Schristos case 'p':
208*946379e7Schristos output_syntax = &output_format_properties;
209*946379e7Schristos break;
210*946379e7Schristos
211*946379e7Schristos case 'P':
212*946379e7Schristos input_syntax = &input_format_properties;
213*946379e7Schristos break;
214*946379e7Schristos
215*946379e7Schristos case 's':
216*946379e7Schristos sort_by_msgid = true;
217*946379e7Schristos break;
218*946379e7Schristos
219*946379e7Schristos case 'S':
220*946379e7Schristos message_print_style_uniforum ();
221*946379e7Schristos break;
222*946379e7Schristos
223*946379e7Schristos case 't':
224*946379e7Schristos to_code = optarg;
225*946379e7Schristos break;
226*946379e7Schristos
227*946379e7Schristos case 'u':
228*946379e7Schristos less_than = 2;
229*946379e7Schristos break;
230*946379e7Schristos
231*946379e7Schristos case 'V':
232*946379e7Schristos do_version = true;
233*946379e7Schristos break;
234*946379e7Schristos
235*946379e7Schristos case 'w':
236*946379e7Schristos {
237*946379e7Schristos int value;
238*946379e7Schristos char *endp;
239*946379e7Schristos value = strtol (optarg, &endp, 10);
240*946379e7Schristos if (endp != optarg)
241*946379e7Schristos message_page_width_set (value);
242*946379e7Schristos }
243*946379e7Schristos break;
244*946379e7Schristos
245*946379e7Schristos case CHAR_MAX + 1:
246*946379e7Schristos use_first = true;
247*946379e7Schristos break;
248*946379e7Schristos
249*946379e7Schristos case CHAR_MAX + 2: /* --no-wrap */
250*946379e7Schristos message_page_width_ignore ();
251*946379e7Schristos break;
252*946379e7Schristos
253*946379e7Schristos case CHAR_MAX + 3: /* --stringtable-input */
254*946379e7Schristos input_syntax = &input_format_stringtable;
255*946379e7Schristos break;
256*946379e7Schristos
257*946379e7Schristos case CHAR_MAX + 4: /* --stringtable-output */
258*946379e7Schristos output_syntax = &output_format_stringtable;
259*946379e7Schristos break;
260*946379e7Schristos
261*946379e7Schristos default:
262*946379e7Schristos usage (EXIT_FAILURE);
263*946379e7Schristos /* NOTREACHED */
264*946379e7Schristos }
265*946379e7Schristos
266*946379e7Schristos /* Version information requested. */
267*946379e7Schristos if (do_version)
268*946379e7Schristos {
269*946379e7Schristos printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
270*946379e7Schristos /* xgettext: no-wrap */
271*946379e7Schristos printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
272*946379e7Schristos This is free software; see the source for copying conditions. There is NO\n\
273*946379e7Schristos warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
274*946379e7Schristos "),
275*946379e7Schristos "2001-2006");
276*946379e7Schristos printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
277*946379e7Schristos exit (EXIT_SUCCESS);
278*946379e7Schristos }
279*946379e7Schristos
280*946379e7Schristos /* Help is requested. */
281*946379e7Schristos if (do_help)
282*946379e7Schristos usage (EXIT_SUCCESS);
283*946379e7Schristos
284*946379e7Schristos /* Verify selected options. */
285*946379e7Schristos if (!line_comment && sort_by_filepos)
286*946379e7Schristos error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
287*946379e7Schristos "--no-location", "--sort-by-file");
288*946379e7Schristos
289*946379e7Schristos if (sort_by_msgid && sort_by_filepos)
290*946379e7Schristos error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
291*946379e7Schristos "--sort-output", "--sort-by-file");
292*946379e7Schristos
293*946379e7Schristos /* Check the message selection criteria for sanity. */
294*946379e7Schristos if (more_than >= less_than || less_than < 2)
295*946379e7Schristos error (EXIT_FAILURE, 0,
296*946379e7Schristos _("impossible selection criteria specified (%d < n < %d)"),
297*946379e7Schristos more_than, less_than);
298*946379e7Schristos
299*946379e7Schristos /* Determine list of files we have to process. */
300*946379e7Schristos if (files_from != NULL)
301*946379e7Schristos file_list = read_names_from_file (files_from);
302*946379e7Schristos else
303*946379e7Schristos file_list = string_list_alloc ();
304*946379e7Schristos /* Append names from command line. */
305*946379e7Schristos for (cnt = optind; cnt < argc; ++cnt)
306*946379e7Schristos string_list_append_unique (file_list, argv[cnt]);
307*946379e7Schristos
308*946379e7Schristos /* Read input files, then filter, convert and merge messages. */
309*946379e7Schristos result =
310*946379e7Schristos catenate_msgdomain_list (file_list, input_syntax,
311*946379e7Schristos output_syntax->requires_utf8 ? "UTF-8" : to_code);
312*946379e7Schristos
313*946379e7Schristos string_list_free (file_list);
314*946379e7Schristos
315*946379e7Schristos /* Sorting the list of messages. */
316*946379e7Schristos if (sort_by_filepos)
317*946379e7Schristos msgdomain_list_sort_by_filepos (result);
318*946379e7Schristos else if (sort_by_msgid)
319*946379e7Schristos msgdomain_list_sort_by_msgid (result);
320*946379e7Schristos
321*946379e7Schristos /* Write the PO file. */
322*946379e7Schristos msgdomain_list_print (result, output_file, output_syntax, force_po, false);
323*946379e7Schristos
324*946379e7Schristos exit (EXIT_SUCCESS);
325*946379e7Schristos }
326*946379e7Schristos
327*946379e7Schristos
328*946379e7Schristos /* Display usage information and exit. */
329*946379e7Schristos static void
usage(int status)330*946379e7Schristos usage (int status)
331*946379e7Schristos {
332*946379e7Schristos if (status != EXIT_SUCCESS)
333*946379e7Schristos fprintf (stderr, _("Try `%s --help' for more information.\n"),
334*946379e7Schristos program_name);
335*946379e7Schristos else
336*946379e7Schristos {
337*946379e7Schristos printf (_("\
338*946379e7Schristos Usage: %s [OPTION] [INPUTFILE]...\n\
339*946379e7Schristos "), program_name);
340*946379e7Schristos printf ("\n");
341*946379e7Schristos /* xgettext: no-wrap */
342*946379e7Schristos printf (_("\
343*946379e7Schristos Concatenates and merges the specified PO files.\n\
344*946379e7Schristos Find messages which are common to two or more of the specified PO files.\n\
345*946379e7Schristos By using the --more-than option, greater commonality may be requested\n\
346*946379e7Schristos before messages are printed. Conversely, the --less-than option may be\n\
347*946379e7Schristos used to specify less commonality before messages are printed (i.e.\n\
348*946379e7Schristos --less-than=2 will only print the unique messages). Translations,\n\
349*946379e7Schristos comments and extract comments will be cumulated, except that if --use-first\n\
350*946379e7Schristos is specified, they will be taken from the first PO file to define them.\n\
351*946379e7Schristos File positions from all PO files will be cumulated.\n\
352*946379e7Schristos "));
353*946379e7Schristos printf ("\n");
354*946379e7Schristos printf (_("\
355*946379e7Schristos Mandatory arguments to long options are mandatory for short options too.\n"));
356*946379e7Schristos printf ("\n");
357*946379e7Schristos printf (_("\
358*946379e7Schristos Input file location:\n"));
359*946379e7Schristos printf (_("\
360*946379e7Schristos INPUTFILE ... input files\n"));
361*946379e7Schristos printf (_("\
362*946379e7Schristos -f, --files-from=FILE get list of input files from FILE\n"));
363*946379e7Schristos printf (_("\
364*946379e7Schristos -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n"));
365*946379e7Schristos printf (_("\
366*946379e7Schristos If input file is -, standard input is read.\n"));
367*946379e7Schristos printf ("\n");
368*946379e7Schristos printf (_("\
369*946379e7Schristos Output file location:\n"));
370*946379e7Schristos printf (_("\
371*946379e7Schristos -o, --output-file=FILE write output to specified file\n"));
372*946379e7Schristos printf (_("\
373*946379e7Schristos The results are written to standard output if no output file is specified\n\
374*946379e7Schristos or if it is -.\n"));
375*946379e7Schristos printf ("\n");
376*946379e7Schristos printf (_("\
377*946379e7Schristos Message selection:\n"));
378*946379e7Schristos printf (_("\
379*946379e7Schristos -<, --less-than=NUMBER print messages with less than this many\n\
380*946379e7Schristos definitions, defaults to infinite if not set\n"));
381*946379e7Schristos printf (_("\
382*946379e7Schristos ->, --more-than=NUMBER print messages with more than this many\n\
383*946379e7Schristos definitions, defaults to 0 if not set\n"));
384*946379e7Schristos printf (_("\
385*946379e7Schristos -u, --unique shorthand for --less-than=2, requests\n\
386*946379e7Schristos that only unique messages be printed\n"));
387*946379e7Schristos printf ("\n");
388*946379e7Schristos printf (_("\
389*946379e7Schristos Input file syntax:\n"));
390*946379e7Schristos printf (_("\
391*946379e7Schristos -P, --properties-input input files are in Java .properties syntax\n"));
392*946379e7Schristos printf (_("\
393*946379e7Schristos --stringtable-input input files are in NeXTstep/GNUstep .strings\n\
394*946379e7Schristos syntax\n"));
395*946379e7Schristos printf ("\n");
396*946379e7Schristos printf (_("\
397*946379e7Schristos Output details:\n"));
398*946379e7Schristos printf (_("\
399*946379e7Schristos -t, --to-code=NAME encoding for output\n"));
400*946379e7Schristos printf (_("\
401*946379e7Schristos --use-first use first available translation for each\n\
402*946379e7Schristos message, don't merge several translations\n"));
403*946379e7Schristos printf (_("\
404*946379e7Schristos -e, --no-escape do not use C escapes in output (default)\n"));
405*946379e7Schristos printf (_("\
406*946379e7Schristos -E, --escape use C escapes in output, no extended chars\n"));
407*946379e7Schristos printf (_("\
408*946379e7Schristos --force-po write PO file even if empty\n"));
409*946379e7Schristos printf (_("\
410*946379e7Schristos -i, --indent write the .po file using indented style\n"));
411*946379e7Schristos printf (_("\
412*946379e7Schristos --no-location do not write '#: filename:line' lines\n"));
413*946379e7Schristos printf (_("\
414*946379e7Schristos -n, --add-location generate '#: filename:line' lines (default)\n"));
415*946379e7Schristos printf (_("\
416*946379e7Schristos --strict write out strict Uniforum conforming .po file\n"));
417*946379e7Schristos printf (_("\
418*946379e7Schristos -p, --properties-output write out a Java .properties file\n"));
419*946379e7Schristos printf (_("\
420*946379e7Schristos --stringtable-output write out a NeXTstep/GNUstep .strings file\n"));
421*946379e7Schristos printf (_("\
422*946379e7Schristos -w, --width=NUMBER set output page width\n"));
423*946379e7Schristos printf (_("\
424*946379e7Schristos --no-wrap do not break long message lines, longer than\n\
425*946379e7Schristos the output page width, into several lines\n"));
426*946379e7Schristos printf (_("\
427*946379e7Schristos -s, --sort-output generate sorted output\n"));
428*946379e7Schristos printf (_("\
429*946379e7Schristos -F, --sort-by-file sort output by file location\n"));
430*946379e7Schristos printf ("\n");
431*946379e7Schristos printf (_("\
432*946379e7Schristos Informative output:\n"));
433*946379e7Schristos printf (_("\
434*946379e7Schristos -h, --help display this help and exit\n"));
435*946379e7Schristos printf (_("\
436*946379e7Schristos -V, --version output version information and exit\n"));
437*946379e7Schristos printf ("\n");
438*946379e7Schristos fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"),
439*946379e7Schristos stdout);
440*946379e7Schristos }
441*946379e7Schristos
442*946379e7Schristos exit (status);
443*946379e7Schristos }
444