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