1*946379e7Schristos /* GNU gettext - internationalization aids
2*946379e7Schristos Copyright (C) 1995-1998, 2000-2006 Free Software Foundation, Inc.
3*946379e7Schristos This file was written by Peter Miller <millerp@canb.auug.org.au>
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 #ifdef HAVE_CONFIG_H
20*946379e7Schristos # include <config.h>
21*946379e7Schristos #endif
22*946379e7Schristos
23*946379e7Schristos #include <getopt.h>
24*946379e7Schristos #include <limits.h>
25*946379e7Schristos #include <stdbool.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 "error.h"
33*946379e7Schristos #include "error-progname.h"
34*946379e7Schristos #include "progname.h"
35*946379e7Schristos #include "relocatable.h"
36*946379e7Schristos #include "basename.h"
37*946379e7Schristos #include "message.h"
38*946379e7Schristos #include "exit.h"
39*946379e7Schristos #include "read-catalog.h"
40*946379e7Schristos #include "read-po.h"
41*946379e7Schristos #include "read-properties.h"
42*946379e7Schristos #include "read-stringtable.h"
43*946379e7Schristos #include "msgl-iconv.h"
44*946379e7Schristos #include "c-strstr.h"
45*946379e7Schristos #include "c-strcase.h"
46*946379e7Schristos #include "propername.h"
47*946379e7Schristos #include "gettext.h"
48*946379e7Schristos
49*946379e7Schristos #define _(str) gettext (str)
50*946379e7Schristos
51*946379e7Schristos
52*946379e7Schristos /* Apply the .pot file to each of the domains in the PO file. */
53*946379e7Schristos static bool multi_domain_mode = false;
54*946379e7Schristos
55*946379e7Schristos /* Whether to consider fuzzy messages as translations. */
56*946379e7Schristos static bool include_fuzzies = false;
57*946379e7Schristos
58*946379e7Schristos /* Whether to consider untranslated messages as translations. */
59*946379e7Schristos static bool include_untranslated = false;
60*946379e7Schristos
61*946379e7Schristos /* Long options. */
62*946379e7Schristos static const struct option long_options[] =
63*946379e7Schristos {
64*946379e7Schristos { "directory", required_argument, NULL, 'D' },
65*946379e7Schristos { "help", no_argument, NULL, 'h' },
66*946379e7Schristos { "multi-domain", no_argument, NULL, 'm' },
67*946379e7Schristos { "properties-input", no_argument, NULL, 'P' },
68*946379e7Schristos { "stringtable-input", no_argument, NULL, CHAR_MAX + 1 },
69*946379e7Schristos { "use-fuzzy", no_argument, NULL, CHAR_MAX + 2 },
70*946379e7Schristos { "use-untranslated", no_argument, NULL, CHAR_MAX + 3 },
71*946379e7Schristos { "version", no_argument, NULL, 'V' },
72*946379e7Schristos { NULL, 0, NULL, 0 }
73*946379e7Schristos };
74*946379e7Schristos
75*946379e7Schristos
76*946379e7Schristos /* Forward declaration of local functions. */
77*946379e7Schristos static void usage (int status)
78*946379e7Schristos #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
79*946379e7Schristos __attribute__ ((noreturn))
80*946379e7Schristos #endif
81*946379e7Schristos ;
82*946379e7Schristos static void compare (const char *fn1, const char *fn2,
83*946379e7Schristos catalog_input_format_ty input_syntax);
84*946379e7Schristos
85*946379e7Schristos
86*946379e7Schristos int
main(int argc,char * argv[])87*946379e7Schristos main (int argc, char *argv[])
88*946379e7Schristos {
89*946379e7Schristos int optchar;
90*946379e7Schristos bool do_help;
91*946379e7Schristos bool do_version;
92*946379e7Schristos catalog_input_format_ty input_syntax = &input_format_po;
93*946379e7Schristos
94*946379e7Schristos /* Set program name for messages. */
95*946379e7Schristos set_program_name (argv[0]);
96*946379e7Schristos error_print_progname = maybe_print_progname;
97*946379e7Schristos gram_max_allowed_errors = UINT_MAX;
98*946379e7Schristos
99*946379e7Schristos #ifdef HAVE_SETLOCALE
100*946379e7Schristos /* Set locale via LC_ALL. */
101*946379e7Schristos setlocale (LC_ALL, "");
102*946379e7Schristos #endif
103*946379e7Schristos
104*946379e7Schristos /* Set the text message domain. */
105*946379e7Schristos bindtextdomain (PACKAGE, relocate (LOCALEDIR));
106*946379e7Schristos bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
107*946379e7Schristos textdomain (PACKAGE);
108*946379e7Schristos
109*946379e7Schristos /* Ensure that write errors on stdout are detected. */
110*946379e7Schristos atexit (close_stdout);
111*946379e7Schristos
112*946379e7Schristos do_help = false;
113*946379e7Schristos do_version = false;
114*946379e7Schristos while ((optchar = getopt_long (argc, argv, "D:hmPV", long_options, NULL))
115*946379e7Schristos != EOF)
116*946379e7Schristos switch (optchar)
117*946379e7Schristos {
118*946379e7Schristos case '\0': /* long option */
119*946379e7Schristos break;
120*946379e7Schristos
121*946379e7Schristos case 'D':
122*946379e7Schristos dir_list_append (optarg);
123*946379e7Schristos break;
124*946379e7Schristos
125*946379e7Schristos case 'h':
126*946379e7Schristos do_help = true;
127*946379e7Schristos break;
128*946379e7Schristos
129*946379e7Schristos case 'm':
130*946379e7Schristos multi_domain_mode = true;
131*946379e7Schristos break;
132*946379e7Schristos
133*946379e7Schristos case 'P':
134*946379e7Schristos input_syntax = &input_format_properties;
135*946379e7Schristos break;
136*946379e7Schristos
137*946379e7Schristos case 'V':
138*946379e7Schristos do_version = true;
139*946379e7Schristos break;
140*946379e7Schristos
141*946379e7Schristos case CHAR_MAX + 1: /* --stringtable-input */
142*946379e7Schristos input_syntax = &input_format_stringtable;
143*946379e7Schristos break;
144*946379e7Schristos
145*946379e7Schristos case CHAR_MAX + 2: /* --use-fuzzy */
146*946379e7Schristos include_fuzzies = true;
147*946379e7Schristos break;
148*946379e7Schristos
149*946379e7Schristos case CHAR_MAX + 3: /* --use-untranslated */
150*946379e7Schristos include_untranslated = true;
151*946379e7Schristos break;
152*946379e7Schristos
153*946379e7Schristos default:
154*946379e7Schristos usage (EXIT_FAILURE);
155*946379e7Schristos break;
156*946379e7Schristos }
157*946379e7Schristos
158*946379e7Schristos /* Version information is requested. */
159*946379e7Schristos if (do_version)
160*946379e7Schristos {
161*946379e7Schristos printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
162*946379e7Schristos /* xgettext: no-wrap */
163*946379e7Schristos printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
164*946379e7Schristos This is free software; see the source for copying conditions. There is NO\n\
165*946379e7Schristos warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
166*946379e7Schristos "),
167*946379e7Schristos "1995-1998, 2000-2006");
168*946379e7Schristos printf (_("Written by %s.\n"), proper_name ("Peter Miller"));
169*946379e7Schristos exit (EXIT_SUCCESS);
170*946379e7Schristos }
171*946379e7Schristos
172*946379e7Schristos /* Help is requested. */
173*946379e7Schristos if (do_help)
174*946379e7Schristos usage (EXIT_SUCCESS);
175*946379e7Schristos
176*946379e7Schristos /* Test whether we have an .po file name as argument. */
177*946379e7Schristos if (optind >= argc)
178*946379e7Schristos {
179*946379e7Schristos error (EXIT_SUCCESS, 0, _("no input files given"));
180*946379e7Schristos usage (EXIT_FAILURE);
181*946379e7Schristos }
182*946379e7Schristos if (optind + 2 != argc)
183*946379e7Schristos {
184*946379e7Schristos error (EXIT_SUCCESS, 0, _("exactly 2 input files required"));
185*946379e7Schristos usage (EXIT_FAILURE);
186*946379e7Schristos }
187*946379e7Schristos
188*946379e7Schristos /* compare the two files */
189*946379e7Schristos compare (argv[optind], argv[optind + 1], input_syntax);
190*946379e7Schristos exit (EXIT_SUCCESS);
191*946379e7Schristos }
192*946379e7Schristos
193*946379e7Schristos
194*946379e7Schristos /* Display usage information and exit. */
195*946379e7Schristos static void
usage(int status)196*946379e7Schristos usage (int status)
197*946379e7Schristos {
198*946379e7Schristos if (status != EXIT_SUCCESS)
199*946379e7Schristos fprintf (stderr, _("Try `%s --help' for more information.\n"),
200*946379e7Schristos program_name);
201*946379e7Schristos else
202*946379e7Schristos {
203*946379e7Schristos printf (_("\
204*946379e7Schristos Usage: %s [OPTION] def.po ref.pot\n\
205*946379e7Schristos "), program_name);
206*946379e7Schristos printf ("\n");
207*946379e7Schristos /* xgettext: no-wrap */
208*946379e7Schristos printf (_("\
209*946379e7Schristos Compare two Uniforum style .po files to check that both contain the same\n\
210*946379e7Schristos set of msgid strings. The def.po file is an existing PO file with the\n\
211*946379e7Schristos translations. The ref.pot file is the last created PO file, or a PO Template\n\
212*946379e7Schristos file (generally created by xgettext). This is useful for checking that\n\
213*946379e7Schristos you have translated each and every message in your program. Where an exact\n\
214*946379e7Schristos match cannot be found, fuzzy matching is used to produce better diagnostics.\n\
215*946379e7Schristos "));
216*946379e7Schristos printf ("\n");
217*946379e7Schristos printf (_("\
218*946379e7Schristos Mandatory arguments to long options are mandatory for short options too.\n"));
219*946379e7Schristos printf ("\n");
220*946379e7Schristos printf (_("\
221*946379e7Schristos Input file location:\n"));
222*946379e7Schristos printf (_("\
223*946379e7Schristos def.po translations\n"));
224*946379e7Schristos printf (_("\
225*946379e7Schristos ref.pot references to the sources\n"));
226*946379e7Schristos printf (_("\
227*946379e7Schristos -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n"));
228*946379e7Schristos printf ("\n");
229*946379e7Schristos printf (_("\
230*946379e7Schristos Operation modifiers:\n"));
231*946379e7Schristos printf (_("\
232*946379e7Schristos -m, --multi-domain apply ref.pot to each of the domains in def.po\n"));
233*946379e7Schristos printf (_("\
234*946379e7Schristos --use-fuzzy consider fuzzy entries\n"));
235*946379e7Schristos printf (_("\
236*946379e7Schristos --use-untranslated consider untranslated entries\n"));
237*946379e7Schristos printf ("\n");
238*946379e7Schristos printf (_("\
239*946379e7Schristos Input file syntax:\n"));
240*946379e7Schristos printf (_("\
241*946379e7Schristos -P, --properties-input input files are in Java .properties syntax\n"));
242*946379e7Schristos printf (_("\
243*946379e7Schristos --stringtable-input input files are in NeXTstep/GNUstep .strings\n\
244*946379e7Schristos syntax\n"));
245*946379e7Schristos printf ("\n");
246*946379e7Schristos printf (_("\
247*946379e7Schristos Informative output:\n"));
248*946379e7Schristos printf (_("\
249*946379e7Schristos -h, --help display this help and exit\n"));
250*946379e7Schristos printf (_("\
251*946379e7Schristos -V, --version output version information and exit\n"));
252*946379e7Schristos printf ("\n");
253*946379e7Schristos fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), stdout);
254*946379e7Schristos }
255*946379e7Schristos
256*946379e7Schristos exit (status);
257*946379e7Schristos }
258*946379e7Schristos
259*946379e7Schristos
260*946379e7Schristos /* Return true if a message should be kept. */
261*946379e7Schristos static bool
is_message_selected(const message_ty * mp)262*946379e7Schristos is_message_selected (const message_ty *mp)
263*946379e7Schristos {
264*946379e7Schristos /* Always keep the header entry. */
265*946379e7Schristos if (is_header (mp))
266*946379e7Schristos return true;
267*946379e7Schristos
268*946379e7Schristos return !mp->obsolete;
269*946379e7Schristos }
270*946379e7Schristos
271*946379e7Schristos
272*946379e7Schristos /* Remove obsolete messages from a message list. Return the modified list. */
273*946379e7Schristos static msgdomain_list_ty *
remove_obsoletes(msgdomain_list_ty * mdlp)274*946379e7Schristos remove_obsoletes (msgdomain_list_ty *mdlp)
275*946379e7Schristos {
276*946379e7Schristos size_t k;
277*946379e7Schristos
278*946379e7Schristos for (k = 0; k < mdlp->nitems; k++)
279*946379e7Schristos message_list_remove_if_not (mdlp->item[k]->messages, is_message_selected);
280*946379e7Schristos
281*946379e7Schristos return mdlp;
282*946379e7Schristos }
283*946379e7Schristos
284*946379e7Schristos
285*946379e7Schristos static void
match_domain(const char * fn1,const char * fn2,message_list_ty * defmlp,message_list_ty * refmlp,int * nerrors)286*946379e7Schristos match_domain (const char *fn1, const char *fn2,
287*946379e7Schristos message_list_ty *defmlp, message_list_ty *refmlp,
288*946379e7Schristos int *nerrors)
289*946379e7Schristos {
290*946379e7Schristos size_t j;
291*946379e7Schristos
292*946379e7Schristos for (j = 0; j < refmlp->nitems; j++)
293*946379e7Schristos {
294*946379e7Schristos message_ty *refmsg;
295*946379e7Schristos message_ty *defmsg;
296*946379e7Schristos
297*946379e7Schristos refmsg = refmlp->item[j];
298*946379e7Schristos
299*946379e7Schristos /* See if it is in the other file. */
300*946379e7Schristos defmsg = message_list_search (defmlp, refmsg->msgctxt, refmsg->msgid);
301*946379e7Schristos if (defmsg)
302*946379e7Schristos {
303*946379e7Schristos if (!include_untranslated && defmsg->msgstr[0] == '\0')
304*946379e7Schristos {
305*946379e7Schristos (*nerrors)++;
306*946379e7Schristos po_gram_error_at_line (&defmsg->pos, _("\
307*946379e7Schristos this message is untranslated"));
308*946379e7Schristos }
309*946379e7Schristos else if (!include_fuzzies && defmsg->is_fuzzy && !is_header (defmsg))
310*946379e7Schristos {
311*946379e7Schristos (*nerrors)++;
312*946379e7Schristos po_gram_error_at_line (&defmsg->pos, _("\
313*946379e7Schristos this message needs to be reviewed by the translator"));
314*946379e7Schristos }
315*946379e7Schristos else
316*946379e7Schristos defmsg->used = 1;
317*946379e7Schristos }
318*946379e7Schristos else
319*946379e7Schristos {
320*946379e7Schristos /* If the message was not defined at all, try to find a very
321*946379e7Schristos similar message, it could be a typo, or the suggestion may
322*946379e7Schristos help. */
323*946379e7Schristos (*nerrors)++;
324*946379e7Schristos defmsg =
325*946379e7Schristos message_list_search_fuzzy (defmlp, refmsg->msgctxt, refmsg->msgid);
326*946379e7Schristos if (defmsg)
327*946379e7Schristos {
328*946379e7Schristos po_gram_error_at_line (&refmsg->pos, _("\
329*946379e7Schristos this message is used but not defined..."));
330*946379e7Schristos error_message_count--;
331*946379e7Schristos po_gram_error_at_line (&defmsg->pos, _("\
332*946379e7Schristos ...but this definition is similar"));
333*946379e7Schristos defmsg->used = 1;
334*946379e7Schristos }
335*946379e7Schristos else
336*946379e7Schristos po_gram_error_at_line (&refmsg->pos, _("\
337*946379e7Schristos this message is used but not defined in %s"), fn1);
338*946379e7Schristos }
339*946379e7Schristos }
340*946379e7Schristos }
341*946379e7Schristos
342*946379e7Schristos
343*946379e7Schristos static void
compare(const char * fn1,const char * fn2,catalog_input_format_ty input_syntax)344*946379e7Schristos compare (const char *fn1, const char *fn2, catalog_input_format_ty input_syntax)
345*946379e7Schristos {
346*946379e7Schristos msgdomain_list_ty *def;
347*946379e7Schristos msgdomain_list_ty *ref;
348*946379e7Schristos int nerrors;
349*946379e7Schristos size_t j, k;
350*946379e7Schristos message_list_ty *empty_list;
351*946379e7Schristos
352*946379e7Schristos /* This is the master file, created by a human. */
353*946379e7Schristos def = remove_obsoletes (read_catalog_file (fn1, input_syntax));
354*946379e7Schristos
355*946379e7Schristos /* This is the generated file, created by groping the sources with
356*946379e7Schristos the xgettext program. */
357*946379e7Schristos ref = remove_obsoletes (read_catalog_file (fn2, input_syntax));
358*946379e7Schristos
359*946379e7Schristos /* The references file can be either in ASCII or in UTF-8. If it is
360*946379e7Schristos in UTF-8, we have to convert the definitions to UTF-8 as well. */
361*946379e7Schristos {
362*946379e7Schristos bool was_utf8 = false;
363*946379e7Schristos for (k = 0; k < ref->nitems; k++)
364*946379e7Schristos {
365*946379e7Schristos message_list_ty *mlp = ref->item[k]->messages;
366*946379e7Schristos
367*946379e7Schristos for (j = 0; j < mlp->nitems; j++)
368*946379e7Schristos if (is_header (mlp->item[j]) /* && !mlp->item[j]->obsolete */)
369*946379e7Schristos {
370*946379e7Schristos const char *header = mlp->item[j]->msgstr;
371*946379e7Schristos
372*946379e7Schristos if (header != NULL)
373*946379e7Schristos {
374*946379e7Schristos const char *charsetstr = c_strstr (header, "charset=");
375*946379e7Schristos
376*946379e7Schristos if (charsetstr != NULL)
377*946379e7Schristos {
378*946379e7Schristos size_t len;
379*946379e7Schristos
380*946379e7Schristos charsetstr += strlen ("charset=");
381*946379e7Schristos len = strcspn (charsetstr, " \t\n");
382*946379e7Schristos if (len == strlen ("UTF-8")
383*946379e7Schristos && c_strncasecmp (charsetstr, "UTF-8", len) == 0)
384*946379e7Schristos was_utf8 = true;
385*946379e7Schristos }
386*946379e7Schristos }
387*946379e7Schristos }
388*946379e7Schristos }
389*946379e7Schristos if (was_utf8)
390*946379e7Schristos def = iconv_msgdomain_list (def, "UTF-8", fn1);
391*946379e7Schristos }
392*946379e7Schristos
393*946379e7Schristos empty_list = message_list_alloc (false);
394*946379e7Schristos
395*946379e7Schristos /* Every entry in the xgettext generated file must be matched by a
396*946379e7Schristos (single) entry in the human created file. */
397*946379e7Schristos nerrors = 0;
398*946379e7Schristos if (!multi_domain_mode)
399*946379e7Schristos for (k = 0; k < ref->nitems; k++)
400*946379e7Schristos {
401*946379e7Schristos const char *domain = ref->item[k]->domain;
402*946379e7Schristos message_list_ty *refmlp = ref->item[k]->messages;
403*946379e7Schristos message_list_ty *defmlp;
404*946379e7Schristos
405*946379e7Schristos defmlp = msgdomain_list_sublist (def, domain, false);
406*946379e7Schristos if (defmlp == NULL)
407*946379e7Schristos defmlp = empty_list;
408*946379e7Schristos
409*946379e7Schristos match_domain (fn1, fn2, defmlp, refmlp, &nerrors);
410*946379e7Schristos }
411*946379e7Schristos else
412*946379e7Schristos {
413*946379e7Schristos /* Apply the references messages in the default domain to each of
414*946379e7Schristos the definition domains. */
415*946379e7Schristos message_list_ty *refmlp = ref->item[0]->messages;
416*946379e7Schristos
417*946379e7Schristos for (k = 0; k < def->nitems; k++)
418*946379e7Schristos {
419*946379e7Schristos message_list_ty *defmlp = def->item[k]->messages;
420*946379e7Schristos
421*946379e7Schristos /* Ignore the default message domain if it has no messages. */
422*946379e7Schristos if (k > 0 || defmlp->nitems > 0)
423*946379e7Schristos match_domain (fn1, fn2, defmlp, refmlp, &nerrors);
424*946379e7Schristos }
425*946379e7Schristos }
426*946379e7Schristos
427*946379e7Schristos /* Look for messages in the definition file, which are not present
428*946379e7Schristos in the reference file, indicating messages which defined but not
429*946379e7Schristos used in the program. */
430*946379e7Schristos for (k = 0; k < def->nitems; ++k)
431*946379e7Schristos {
432*946379e7Schristos message_list_ty *defmlp = def->item[k]->messages;
433*946379e7Schristos
434*946379e7Schristos for (j = 0; j < defmlp->nitems; j++)
435*946379e7Schristos {
436*946379e7Schristos message_ty *defmsg = defmlp->item[j];
437*946379e7Schristos
438*946379e7Schristos if (!defmsg->used)
439*946379e7Schristos po_gram_error_at_line (&defmsg->pos,
440*946379e7Schristos _("warning: this message is not used"));
441*946379e7Schristos }
442*946379e7Schristos }
443*946379e7Schristos
444*946379e7Schristos /* Exit with status 1 on any error. */
445*946379e7Schristos if (nerrors > 0)
446*946379e7Schristos error (EXIT_FAILURE, 0,
447*946379e7Schristos ngettext ("found %d fatal error", "found %d fatal errors", nerrors),
448*946379e7Schristos nerrors);
449*946379e7Schristos }
450