1 /* GNU gettext - internationalization aids
2 Copyright (C) 1995-1998, 2000-2005 Free Software Foundation, Inc.
3 This file was written by Peter Miller <millerp@canb.auug.org.au>
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 2, or (at your option)
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.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software Foundation,
17 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
33 #include "error-progname.h"
35 #include "relocatable.h"
40 #include "msgl-iconv.h"
42 #include "c-strcase.h"
45 #define _(str) gettext (str)
48 /* Apply the .pot file to each of the domains in the PO file. */
49 static bool multi_domain_mode
= false;
52 static const struct option long_options
[] =
54 { "directory", required_argument
, NULL
, 'D' },
55 { "help", no_argument
, NULL
, 'h' },
56 { "multi-domain", no_argument
, NULL
, 'm' },
57 { "properties-input", no_argument
, NULL
, 'P' },
58 { "stringtable-input", no_argument
, NULL
, CHAR_MAX
+ 1 },
59 { "version", no_argument
, NULL
, 'V' },
64 /* Forward declaration of local functions. */
65 static void usage (int status
)
66 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
67 __attribute__ ((noreturn
))
70 static void compare (const char *fn1
, const char *fn2
);
74 main (int argc
, char *argv
[])
80 /* Set program name for messages. */
81 set_program_name (argv
[0]);
82 error_print_progname
= maybe_print_progname
;
83 gram_max_allowed_errors
= UINT_MAX
;
86 /* Set locale via LC_ALL. */
87 setlocale (LC_ALL
, "");
90 /* Set the text message domain. */
91 bindtextdomain (PACKAGE
, relocate (LOCALEDIR
));
94 /* Ensure that write errors on stdout are detected. */
95 atexit (close_stdout
);
99 while ((optchar
= getopt_long (argc
, argv
, "D:hmPV", long_options
, NULL
))
103 case '\0': /* long option */
107 dir_list_append (optarg
);
115 multi_domain_mode
= true;
119 input_syntax
= syntax_properties
;
126 case CHAR_MAX
+ 1: /* --stringtable-input */
127 input_syntax
= syntax_stringtable
;
131 usage (EXIT_FAILURE
);
135 /* Version information is requested. */
138 printf ("%s (GNU %s) %s\n", basename (program_name
), PACKAGE
, VERSION
);
139 /* xgettext: no-wrap */
140 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
141 This is free software; see the source for copying conditions. There is NO\n\
142 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
144 "1995-1998, 2000-2005");
145 printf (_("Written by %s.\n"), "Peter Miller");
149 /* Help is requested. */
151 usage (EXIT_SUCCESS
);
153 /* Test whether we have an .po file name as argument. */
156 error (EXIT_SUCCESS
, 0, _("no input files given"));
157 usage (EXIT_FAILURE
);
159 if (optind
+ 2 != argc
)
161 error (EXIT_SUCCESS
, 0, _("exactly 2 input files required"));
162 usage (EXIT_FAILURE
);
165 /* compare the two files */
166 compare (argv
[optind
], argv
[optind
+ 1]);
171 /* Display usage information and exit. */
175 if (status
!= EXIT_SUCCESS
)
176 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
181 Usage: %s [OPTION] def.po ref.pot\n\
184 /* xgettext: no-wrap */
186 Compare two Uniforum style .po files to check that both contain the same\n\
187 set of msgid strings. The def.po file is an existing PO file with the\n\
188 translations. The ref.pot file is the last created PO file, or a PO Template\n\
189 file (generally created by xgettext). This is useful for checking that\n\
190 you have translated each and every message in your program. Where an exact\n\
191 match cannot be found, fuzzy matching is used to produce better diagnostics.\n\
195 Mandatory arguments to long options are mandatory for short options too.\n"));
198 Input file location:\n"));
200 def.po translations\n"));
202 ref.pot references to the sources\n"));
204 -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n"));
207 Operation modifiers:\n"));
209 -m, --multi-domain apply ref.pot to each of the domains in def.po\n"));
212 Input file syntax:\n"));
214 -P, --properties-input input files are in Java .properties syntax\n"));
216 --stringtable-input input files are in NeXTstep/GNUstep .strings\n\
220 Informative output:\n"));
222 -h, --help display this help and exit\n"));
224 -V, --version output version information and exit\n"));
226 fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), stdout
);
233 /* Return true if a message should be kept. */
235 is_message_selected (const message_ty
*mp
)
237 /* Always keep the header entry. */
238 if (mp
->msgid
[0] == '\0')
241 return !mp
->obsolete
;
245 /* Remove obsolete messages from a message list. Return the modified list. */
246 static msgdomain_list_ty
*
247 remove_obsoletes (msgdomain_list_ty
*mdlp
)
251 for (k
= 0; k
< mdlp
->nitems
; k
++)
252 message_list_remove_if_not (mdlp
->item
[k
]->messages
, is_message_selected
);
259 match_domain (const char *fn1
, const char *fn2
,
260 message_list_ty
*defmlp
, message_list_ty
*refmlp
,
265 for (j
= 0; j
< refmlp
->nitems
; j
++)
270 refmsg
= refmlp
->item
[j
];
272 /* See if it is in the other file. */
273 defmsg
= message_list_search (defmlp
, refmsg
->msgid
);
278 /* If the message was not defined at all, try to find a very
279 similar message, it could be a typo, or the suggestion may
282 defmsg
= message_list_search_fuzzy (defmlp
, refmsg
->msgid
);
285 po_gram_error_at_line (&refmsg
->pos
, _("\
286 this message is used but not defined..."));
287 po_gram_error_at_line (&defmsg
->pos
, _("\
288 ...but this definition is similar"));
292 po_gram_error_at_line (&refmsg
->pos
, _("\
293 this message is used but not defined in %s"), fn1
);
300 compare (const char *fn1
, const char *fn2
)
302 msgdomain_list_ty
*def
;
303 msgdomain_list_ty
*ref
;
306 message_list_ty
*empty_list
;
308 /* This is the master file, created by a human. */
309 def
= remove_obsoletes (read_po_file (fn1
));
311 /* This is the generated file, created by groping the sources with
312 the xgettext program. */
313 ref
= remove_obsoletes (read_po_file (fn2
));
315 /* The references file can be either in ASCII or in UTF-8. If it is
316 in UTF-8, we have to convert the definitions to UTF-8 as well. */
318 bool was_utf8
= false;
319 for (k
= 0; k
< ref
->nitems
; k
++)
321 message_list_ty
*mlp
= ref
->item
[k
]->messages
;
323 for (j
= 0; j
< mlp
->nitems
; j
++)
324 if (mlp
->item
[j
]->msgid
[0] == '\0' /* && !mlp->item[j]->obsolete */)
326 const char *header
= mlp
->item
[j
]->msgstr
;
330 const char *charsetstr
= strstr (header
, "charset=");
332 if (charsetstr
!= NULL
)
336 charsetstr
+= strlen ("charset=");
337 len
= strcspn (charsetstr
, " \t\n");
338 if (len
== strlen ("UTF-8")
339 && c_strncasecmp (charsetstr
, "UTF-8", len
) == 0)
346 def
= iconv_msgdomain_list (def
, "UTF-8", fn1
);
349 empty_list
= message_list_alloc (false);
351 /* Every entry in the xgettext generated file must be matched by a
352 (single) entry in the human created file. */
354 if (!multi_domain_mode
)
355 for (k
= 0; k
< ref
->nitems
; k
++)
357 const char *domain
= ref
->item
[k
]->domain
;
358 message_list_ty
*refmlp
= ref
->item
[k
]->messages
;
359 message_list_ty
*defmlp
;
361 defmlp
= msgdomain_list_sublist (def
, domain
, false);
365 match_domain (fn1
, fn2
, defmlp
, refmlp
, &nerrors
);
369 /* Apply the references messages in the default domain to each of
370 the definition domains. */
371 message_list_ty
*refmlp
= ref
->item
[0]->messages
;
373 for (k
= 0; k
< def
->nitems
; k
++)
375 message_list_ty
*defmlp
= def
->item
[k
]->messages
;
377 /* Ignore the default message domain if it has no messages. */
378 if (k
> 0 || defmlp
->nitems
> 0)
379 match_domain (fn1
, fn2
, defmlp
, refmlp
, &nerrors
);
383 /* Look for messages in the definition file, which are not present
384 in the reference file, indicating messages which defined but not
385 used in the program. */
386 for (k
= 0; k
< def
->nitems
; ++k
)
388 message_list_ty
*defmlp
= def
->item
[k
]->messages
;
390 for (j
= 0; j
< defmlp
->nitems
; j
++)
392 message_ty
*defmsg
= defmlp
->item
[j
];
395 po_gram_error_at_line (&defmsg
->pos
,
396 _("warning: this message is not used"));
400 /* Exit with status 1 on any error. */
402 error (EXIT_FAILURE
, 0,
403 ngettext ("found %d fatal error", "found %d fatal errors", nerrors
),