1 /* Converts Uniforum style .po files to binary .mo files
2 Copyright (C) 1995-1998, 2000-2005 Free Software Foundation, Inc.
3 Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, April 1995.
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. */
37 #include "error-progname.h"
39 #include "relocatable.h"
44 #include "plural-exp.h"
45 #include "plural-table.h"
51 #include "write-java.h"
52 #include "write-csharp.h"
53 #include "write-resources.h"
54 #include "write-tcl.h"
61 #include "po-charset.h"
63 #define _(str) gettext (str)
65 #define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
67 /* Some platforms don't have the sigjmp_buf type in <setjmp.h>. */
68 #if defined _MSC_VER || defined __MINGW32__
69 /* Native Woe32 API. */
70 # define sigjmp_buf jmp_buf
71 # define sigsetjmp(env,savesigs) setjmp (env)
72 # define siglongjmp longjmp
75 /* We use siginfo to get precise information about the signal.
76 But siginfo doesn't work on Irix 6.5. */
77 #if HAVE_SIGINFO && !defined (__sgi)
78 # define USE_SIGINFO 1
81 /* Contains exit status for case in which no premature exit occurs. */
82 static int exit_status
;
84 /* If true include even fuzzy translations in output file. */
85 static bool include_all
= false;
87 /* Specifies name of the output file. */
88 static const char *output_file_name
;
90 /* Java mode output file specification. */
91 static bool java_mode
;
92 static bool assume_java2
;
93 static const char *java_resource_name
;
94 static const char *java_locale_name
;
95 static const char *java_class_directory
;
97 /* C# mode output file specification. */
98 static bool csharp_mode
;
99 static const char *csharp_resource_name
;
100 static const char *csharp_locale_name
;
101 static const char *csharp_base_directory
;
103 /* C# resources mode output file specification. */
104 static bool csharp_resources_mode
;
106 /* Tcl mode output file specification. */
107 static bool tcl_mode
;
108 static const char *tcl_locale_name
;
109 static const char *tcl_base_directory
;
111 /* Qt mode output file specification. */
114 /* We may have more than one input file. Domains with same names in
115 different files have to merged. So we need a list of tables for
119 /* List for mapping message IDs to message strings. */
120 message_list_ty
*mlp
;
121 /* Name of domain these ID/String pairs are part of. */
122 const char *domain_name
;
123 /* Output file name. */
124 const char *file_name
;
125 /* Link to the next domain. */
126 struct msg_domain
*next
;
128 static struct msg_domain
*domain_list
;
129 static struct msg_domain
*current_domain
;
131 /* Be more verbose. Use only 'fprintf' and 'multiline_warning' but not
132 'error' or 'multiline_error' to emit verbosity messages, because 'error'
133 and 'multiline_error' during PO file parsing cause the program to exit
134 with EXIT_FAILURE. See function lex_end(). */
135 bool verbose
= false;
137 /* If true check strings according to format string rules for the
139 static bool check_format_strings
= false;
141 /* If true check the header entry is present and complete. */
142 static bool check_header
= false;
144 /* Check that domain directives can be satisfied. */
145 static bool check_domain
= false;
147 /* Check that msgfmt's behaviour is semantically compatible with
148 X/Open msgfmt or XView msgfmt. */
149 static bool check_compatibility
= false;
151 /* If true, consider that strings containing an '&' are menu items and
152 the '&' designates a keyboard accelerator, and verify that the translations
153 also have a keyboard accelerator. */
154 static bool check_accelerators
= false;
155 static char accelerator_char
= '&';
157 /* Counters for statistics on translations for the processed files. */
158 static int msgs_translated
;
159 static int msgs_untranslated
;
160 static int msgs_fuzzy
;
162 /* If not zero print statistics about translation at the end. */
163 static int do_statistics
;
166 static const struct option long_options
[] =
168 { "alignment", required_argument
, NULL
, 'a' },
169 { "check", no_argument
, NULL
, 'c' },
170 { "check-accelerators", optional_argument
, NULL
, CHAR_MAX
+ 1 },
171 { "check-compatibility", no_argument
, NULL
, 'C' },
172 { "check-domain", no_argument
, NULL
, CHAR_MAX
+ 2 },
173 { "check-format", no_argument
, NULL
, CHAR_MAX
+ 3 },
174 { "check-header", no_argument
, NULL
, CHAR_MAX
+ 4 },
175 { "csharp", no_argument
, NULL
, CHAR_MAX
+ 10 },
176 { "csharp-resources", no_argument
, NULL
, CHAR_MAX
+ 11 },
177 { "directory", required_argument
, NULL
, 'D' },
178 { "help", no_argument
, NULL
, 'h' },
179 { "java", no_argument
, NULL
, 'j' },
180 { "java2", no_argument
, NULL
, CHAR_MAX
+ 5 },
181 { "locale", required_argument
, NULL
, 'l' },
182 { "no-hash", no_argument
, NULL
, CHAR_MAX
+ 6 },
183 { "output-file", required_argument
, NULL
, 'o' },
184 { "properties-input", no_argument
, NULL
, 'P' },
185 { "qt", no_argument
, NULL
, CHAR_MAX
+ 9 },
186 { "resource", required_argument
, NULL
, 'r' },
187 { "statistics", no_argument
, &do_statistics
, 1 },
188 { "strict", no_argument
, NULL
, 'S' },
189 { "stringtable-input", no_argument
, NULL
, CHAR_MAX
+ 8 },
190 { "tcl", no_argument
, NULL
, CHAR_MAX
+ 7 },
191 { "use-fuzzy", no_argument
, NULL
, 'f' },
192 { "verbose", no_argument
, NULL
, 'v' },
193 { "version", no_argument
, NULL
, 'V' },
198 /* Forward declaration of local functions. */
199 static void usage (int status
)
200 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
201 __attribute__ ((noreturn
))
204 static const char *add_mo_suffix (const char *);
205 static struct msg_domain
*new_domain (const char *name
, const char *file_name
);
206 static bool is_nonobsolete (const message_ty
*mp
);
207 static void check_plural (message_list_ty
*mlp
);
208 static void read_po_file_msgfmt (char *filename
);
212 main (int argc
, char *argv
[])
215 bool do_help
= false;
216 bool do_version
= false;
217 bool strict_uniforum
= false;
218 const char *canon_encoding
;
219 struct msg_domain
*domain
;
221 /* Set default value for global variables. */
222 alignment
= DEFAULT_OUTPUT_ALIGNMENT
;
224 /* Set program name for messages. */
225 set_program_name (argv
[0]);
226 error_print_progname
= maybe_print_progname
;
227 error_one_per_line
= 1;
228 exit_status
= EXIT_SUCCESS
;
230 #ifdef HAVE_SETLOCALE
231 /* Set locale via LC_ALL. */
232 setlocale (LC_ALL
, "");
235 /* Set the text message domain. */
236 bindtextdomain (PACKAGE
, relocate (LOCALEDIR
));
237 textdomain (PACKAGE
);
239 /* Ensure that write errors on stdout are detected. */
240 atexit (close_stdout
);
242 while ((opt
= getopt_long (argc
, argv
, "a:cCd:D:fhjl:o:Pr:vV", long_options
,
247 case '\0': /* Long option. */
252 size_t new_align
= strtoul (optarg
, &endp
, 0);
255 alignment
= new_align
;
260 check_format_strings
= true;
264 check_compatibility
= true;
267 java_class_directory
= optarg
;
268 csharp_base_directory
= optarg
;
269 tcl_base_directory
= optarg
;
272 dir_list_append (optarg
);
284 java_locale_name
= optarg
;
285 csharp_locale_name
= optarg
;
286 tcl_locale_name
= optarg
;
289 output_file_name
= optarg
;
292 input_syntax
= syntax_properties
;
295 java_resource_name
= optarg
;
296 csharp_resource_name
= optarg
;
299 strict_uniforum
= true;
307 case CHAR_MAX
+ 1: /* --check-accelerators */
308 check_accelerators
= true;
311 if (optarg
[0] != '\0' && ispunct ((unsigned char) optarg
[0])
312 && optarg
[1] == '\0')
313 accelerator_char
= optarg
[0];
315 error (EXIT_FAILURE
, 0,
316 _("the argument to %s should be a single punctuation character"),
317 "--check-accelerators");
320 case CHAR_MAX
+ 2: /* --check-domain */
323 case CHAR_MAX
+ 3: /* --check-format */
324 check_format_strings
= true;
326 case CHAR_MAX
+ 4: /* --check-header */
329 case CHAR_MAX
+ 5: /* --java2 */
333 case CHAR_MAX
+ 6: /* --no-hash */
334 no_hash_table
= true;
336 case CHAR_MAX
+ 7: /* --tcl */
339 case CHAR_MAX
+ 8: /* --stringtable-input */
340 input_syntax
= syntax_stringtable
;
342 case CHAR_MAX
+ 9: /* --qt */
345 case CHAR_MAX
+ 10: /* --csharp */
348 case CHAR_MAX
+ 11: /* --csharp-resources */
349 csharp_resources_mode
= true;
352 usage (EXIT_FAILURE
);
356 /* Version information is requested. */
359 printf ("%s (GNU %s) %s\n", basename (program_name
), PACKAGE
, VERSION
);
360 /* xgettext: no-wrap */
361 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
362 This is free software; see the source for copying conditions. There is NO\n\
363 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
365 "1995-1998, 2000-2005");
366 printf (_("Written by %s.\n"), "Ulrich Drepper");
370 /* Help is requested. */
372 usage (EXIT_SUCCESS
);
374 /* Test whether we have a .po file name as argument. */
377 error (EXIT_SUCCESS
, 0, _("no input file given"));
378 usage (EXIT_FAILURE
);
381 /* Check for contradicting options. */
385 | (csharp_mode
? 2 : 0)
386 | (csharp_resources_mode
? 4 : 0)
388 | (qt_mode
? 16 : 0);
389 static const char *mode_options
[] =
390 { "--java", "--csharp", "--csharp-resources", "--tcl", "--qt" };
391 /* More than one bit set? */
392 if (modes
& (modes
- 1))
394 const char *first_option
;
395 const char *second_option
;
398 if (modes
& (1 << i
))
400 first_option
= mode_options
[i
];
401 for (i
= i
+ 1; ; i
++)
402 if (modes
& (1 << i
))
404 second_option
= mode_options
[i
];
405 error (EXIT_FAILURE
, 0, _("%s and %s are mutually exclusive"),
406 first_option
, second_option
);
411 if (output_file_name
!= NULL
)
413 error (EXIT_FAILURE
, 0, _("%s and %s are mutually exclusive"),
414 "--java", "--output-file");
416 if (java_class_directory
== NULL
)
418 error (EXIT_SUCCESS
, 0,
419 _("%s requires a \"-d directory\" specification"),
421 usage (EXIT_FAILURE
);
424 else if (csharp_mode
)
426 if (output_file_name
!= NULL
)
428 error (EXIT_FAILURE
, 0, _("%s and %s are mutually exclusive"),
429 "--csharp", "--output-file");
431 if (csharp_locale_name
== NULL
)
433 error (EXIT_SUCCESS
, 0,
434 _("%s requires a \"-l locale\" specification"),
436 usage (EXIT_FAILURE
);
438 if (csharp_base_directory
== NULL
)
440 error (EXIT_SUCCESS
, 0,
441 _("%s requires a \"-d directory\" specification"),
443 usage (EXIT_FAILURE
);
448 if (output_file_name
!= NULL
)
450 error (EXIT_FAILURE
, 0, _("%s and %s are mutually exclusive"),
451 "--tcl", "--output-file");
453 if (tcl_locale_name
== NULL
)
455 error (EXIT_SUCCESS
, 0,
456 _("%s requires a \"-l locale\" specification"),
458 usage (EXIT_FAILURE
);
460 if (tcl_base_directory
== NULL
)
462 error (EXIT_SUCCESS
, 0,
463 _("%s requires a \"-d directory\" specification"),
465 usage (EXIT_FAILURE
);
470 if (java_resource_name
!= NULL
)
472 error (EXIT_SUCCESS
, 0, _("%s is only valid with %s or %s"),
473 "--resource", "--java", "--csharp");
474 usage (EXIT_FAILURE
);
476 if (java_locale_name
!= NULL
)
478 error (EXIT_SUCCESS
, 0, _("%s is only valid with %s, %s or %s"),
479 "--locale", "--java", "--csharp", "--tcl");
480 usage (EXIT_FAILURE
);
482 if (java_class_directory
!= NULL
)
484 error (EXIT_SUCCESS
, 0, _("%s is only valid with %s, %s or %s"),
485 "-d", "--java", "--csharp", "--tcl");
486 usage (EXIT_FAILURE
);
490 /* The -o option determines the name of the domain and therefore
492 if (output_file_name
!= NULL
)
494 new_domain (output_file_name
,
495 strict_uniforum
&& !csharp_resources_mode
&& !qt_mode
496 ? add_mo_suffix (output_file_name
)
499 /* Process all given .po files. */
500 while (argc
> optind
)
502 /* Remember that we currently have not specified any domain. This
503 is of course not true when we saw the -o option. */
504 if (output_file_name
== NULL
)
505 current_domain
= NULL
;
507 /* And process the input file. */
508 read_po_file_msgfmt (argv
[optind
]);
513 /* We know a priori that properties_parse() and stringtable_parse() convert
516 (input_syntax
== syntax_properties
|| input_syntax
== syntax_stringtable
520 /* Remove obsolete messages. They were only needed for duplicate
522 for (domain
= domain_list
; domain
!= NULL
; domain
= domain
->next
)
523 message_list_remove_if_not (domain
->mlp
, is_nonobsolete
);
525 /* Check the plural expression is present if needed and has valid syntax. */
527 for (domain
= domain_list
; domain
!= NULL
; domain
= domain
->next
)
528 check_plural (domain
->mlp
);
530 /* Now write out all domains. */
531 for (domain
= domain_list
; domain
!= NULL
; domain
= domain
->next
)
535 if (msgdomain_write_java (domain
->mlp
, canon_encoding
,
536 java_resource_name
, java_locale_name
,
537 java_class_directory
, assume_java2
))
538 exit_status
= EXIT_FAILURE
;
540 else if (csharp_mode
)
542 if (msgdomain_write_csharp (domain
->mlp
, canon_encoding
,
543 csharp_resource_name
, csharp_locale_name
,
544 csharp_base_directory
))
545 exit_status
= EXIT_FAILURE
;
547 else if (csharp_resources_mode
)
549 if (msgdomain_write_csharp_resources (domain
->mlp
, canon_encoding
,
552 exit_status
= EXIT_FAILURE
;
556 if (msgdomain_write_tcl (domain
->mlp
, canon_encoding
,
557 tcl_locale_name
, tcl_base_directory
))
558 exit_status
= EXIT_FAILURE
;
562 if (msgdomain_write_qt (domain
->mlp
, canon_encoding
,
563 domain
->domain_name
, domain
->file_name
))
564 exit_status
= EXIT_FAILURE
;
568 if (msgdomain_write_mo (domain
->mlp
, domain
->domain_name
,
570 exit_status
= EXIT_FAILURE
;
573 /* List is not used anymore. */
574 message_list_free (domain
->mlp
);
577 /* Print statistics if requested. */
578 if (verbose
|| do_statistics
)
581 ngettext ("%d translated message", "%d translated messages",
586 ngettext (", %d fuzzy translation", ", %d fuzzy translations",
589 if (msgs_untranslated
> 0)
591 ngettext (", %d untranslated message",
592 ", %d untranslated messages",
595 fputs (".\n", stderr
);
602 /* Display usage information and exit. */
606 if (status
!= EXIT_SUCCESS
)
607 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
612 Usage: %s [OPTION] filename.po ...\n\
616 Generate binary message catalog from textual translation description.\n\
619 /* xgettext: no-wrap */
621 Mandatory arguments to long options are mandatory for short options too.\n\
622 Similarly for optional arguments.\n\
626 Input file location:\n"));
628 filename.po ... input files\n"));
630 -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n"));
632 If input file is -, standard input is read.\n"));
635 Operation mode:\n"));
637 -j, --java Java mode: generate a Java ResourceBundle class\n"));
639 --java2 like --java, and assume Java2 (JDK 1.2 or higher)\n"));
641 --csharp C# mode: generate a .NET .dll file\n"));
643 --csharp-resources C# resources mode: generate a .NET .resources file\n"));
645 --tcl Tcl mode: generate a tcl/msgcat .msg file\n"));
647 --qt Qt mode: generate a Qt .qm file\n"));
650 Output file location:\n"));
652 -o, --output-file=FILE write output to specified file\n"));
654 --strict enable strict Uniforum mode\n"));
656 If output file is -, output is written to standard output.\n"));
659 Output file location in Java mode:\n"));
661 -r, --resource=RESOURCE resource name\n"));
663 -l, --locale=LOCALE locale name, either language or language_COUNTRY\n"));
665 -d DIRECTORY base directory of classes directory hierarchy\n"));
667 The class name is determined by appending the locale name to the resource name,\n\
668 separated with an underscore. The -d option is mandatory. The class is\n\
669 written under the specified directory.\n\
673 Output file location in C# mode:\n"));
675 -r, --resource=RESOURCE resource name\n"));
677 -l, --locale=LOCALE locale name, either language or language_COUNTRY\n"));
679 -d DIRECTORY base directory for locale dependent .dll files\n"));
681 The -l and -d options are mandatory. The .dll file is written in a\n\
682 subdirectory of the specified directory whose name depends on the locale.\n"));
685 Output file location in Tcl mode:\n"));
687 -l, --locale=LOCALE locale name, either language or language_COUNTRY\n"));
689 -d DIRECTORY base directory of .msg message catalogs\n"));
691 The -l and -d options are mandatory. The .msg file is written in the\n\
692 specified directory.\n"));
695 Input file syntax:\n"));
697 -P, --properties-input input files are in Java .properties syntax\n"));
699 --stringtable-input input files are in NeXTstep/GNUstep .strings\n\
703 Input file interpretation:\n"));
705 -c, --check perform all the checks implied by\n\
706 --check-format, --check-header, --check-domain\n"));
708 --check-format check language dependent format strings\n"));
710 --check-header verify presence and contents of the header entry\n"));
712 --check-domain check for conflicts between domain directives\n\
713 and the --output-file option\n"));
715 -C, --check-compatibility check that GNU msgfmt behaves like X/Open msgfmt\n"));
717 --check-accelerators[=CHAR] check presence of keyboard accelerators for\n\
720 -f, --use-fuzzy use fuzzy entries in output\n"));
723 Output details:\n"));
725 -a, --alignment=NUMBER align strings to NUMBER bytes (default: %d)\n"), DEFAULT_OUTPUT_ALIGNMENT
);
727 --no-hash binary file will not include the hash table\n"));
730 Informative output:\n"));
732 -h, --help display this help and exit\n"));
734 -V, --version output version information and exit\n"));
736 --statistics print statistics about translations\n"));
738 -v, --verbose increase verbosity level\n"));
740 fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), stdout
);
748 add_mo_suffix (const char *fname
)
753 len
= strlen (fname
);
754 if (len
> 3 && memcmp (fname
+ len
- 3, ".mo", 3) == 0)
756 if (len
> 4 && memcmp (fname
+ len
- 4, ".gmo", 4) == 0)
758 result
= (char *) xmalloc (len
+ 4);
759 stpcpy (stpcpy (result
, fname
), ".mo");
764 static struct msg_domain
*
765 new_domain (const char *name
, const char *file_name
)
767 struct msg_domain
**p_dom
= &domain_list
;
769 while (*p_dom
!= NULL
&& strcmp (name
, (*p_dom
)->domain_name
) != 0)
770 p_dom
= &(*p_dom
)->next
;
774 struct msg_domain
*domain
;
776 domain
= (struct msg_domain
*) xmalloc (sizeof (struct msg_domain
));
777 domain
->mlp
= message_list_alloc (true);
778 domain
->domain_name
= name
;
779 domain
->file_name
= file_name
;
789 is_nonobsolete (const message_ty
*mp
)
791 return !mp
->obsolete
;
795 static sigjmp_buf sigfpe_exit
;
799 static int sigfpe_code
;
801 /* Signal handler called in case of arithmetic exception (e.g. division
802 by zero) during plural_eval. */
804 sigfpe_handler (int sig
, siginfo_t
*sip
, void *scp
)
806 sigfpe_code
= sip
->si_code
;
807 siglongjmp (sigfpe_exit
, 1);
812 /* Signal handler called in case of arithmetic exception (e.g. division
813 by zero) during plural_eval. */
815 sigfpe_handler (int sig
)
817 siglongjmp (sigfpe_exit
, 1);
823 install_sigfpe_handler ()
826 struct sigaction action
;
827 action
.sa_sigaction
= sigfpe_handler
;
828 action
.sa_flags
= SA_SIGINFO
;
829 sigemptyset (&action
.sa_mask
);
830 sigaction (SIGFPE
, &action
, (struct sigaction
*) NULL
);
832 signal (SIGFPE
, sigfpe_handler
);
837 uninstall_sigfpe_handler ()
840 struct sigaction action
;
841 action
.sa_handler
= SIG_DFL
;
843 sigemptyset (&action
.sa_mask
);
844 sigaction (SIGFPE
, &action
, (struct sigaction
*) NULL
);
846 signal (SIGFPE
, SIG_DFL
);
850 /* Check the values returned by plural_eval. */
852 check_plural_eval (struct expression
*plural_expr
,
853 unsigned long nplurals_value
,
854 const lex_pos_ty
*header_pos
)
856 if (sigsetjmp (sigfpe_exit
, 1) == 0)
860 /* Protect against arithmetic exceptions. */
861 install_sigfpe_handler ();
863 for (n
= 0; n
<= 1000; n
++)
865 unsigned long val
= plural_eval (plural_expr
, n
);
869 /* End of protection against arithmetic exceptions. */
870 uninstall_sigfpe_handler ();
872 error_with_progname
= false;
874 header_pos
->file_name
, header_pos
->line_number
,
875 _("plural expression can produce negative values"));
876 error_with_progname
= true;
877 exit_status
= EXIT_FAILURE
;
880 else if (val
>= nplurals_value
)
882 /* End of protection against arithmetic exceptions. */
883 uninstall_sigfpe_handler ();
885 error_with_progname
= false;
887 header_pos
->file_name
, header_pos
->line_number
,
888 _("nplurals = %lu but plural expression can produce values as large as %lu"),
889 nplurals_value
, val
);
890 error_with_progname
= true;
891 exit_status
= EXIT_FAILURE
;
896 /* End of protection against arithmetic exceptions. */
897 uninstall_sigfpe_handler ();
901 /* Caught an arithmetic exception. */
904 /* End of protection against arithmetic exceptions. */
905 uninstall_sigfpe_handler ();
914 /* xgettext: c-format */
915 msg
= _("plural expression can produce division by zero");
920 /* xgettext: c-format */
921 msg
= _("plural expression can produce integer overflow");
926 /* xgettext: c-format */
927 msg
= _("plural expression can produce arithmetic exceptions, possibly division by zero");
930 error_with_progname
= false;
931 error_at_line (0, 0, header_pos
->file_name
, header_pos
->line_number
, msg
);
932 error_with_progname
= true;
933 exit_status
= EXIT_FAILURE
;
938 /* Perform plural expression checking. */
940 check_plural (message_list_ty
*mlp
)
942 const lex_pos_ty
*has_plural
;
943 unsigned long min_nplurals
;
944 const lex_pos_ty
*min_pos
;
945 unsigned long max_nplurals
;
946 const lex_pos_ty
*max_pos
;
950 /* Determine whether mlp has plural entries. */
952 min_nplurals
= ULONG_MAX
;
956 for (j
= 0; j
< mlp
->nitems
; j
++)
958 message_ty
*mp
= mlp
->item
[j
];
960 if (mp
->msgid_plural
!= NULL
)
966 if (has_plural
== NULL
)
967 has_plural
= &mp
->pos
;
970 for (p
= mp
->msgstr
, p_end
= p
+ mp
->msgstr_len
;
974 if (min_nplurals
> n
)
979 if (max_nplurals
> n
)
987 /* Look at the plural entry for this domain.
988 Cf, function extract_plural_expression. */
989 header
= message_list_search (mlp
, "");
992 const char *nullentry
;
994 const char *nplurals
;
995 bool try_to_help
= false;
997 nullentry
= header
->msgstr
;
999 plural
= strstr (nullentry
, "plural=");
1000 nplurals
= strstr (nullentry
, "nplurals=");
1001 if (plural
== NULL
&& has_plural
!= NULL
)
1003 error_with_progname
= false;
1004 error_at_line (0, 0, has_plural
->file_name
, has_plural
->line_number
,
1005 _("message catalog has plural form translations..."));
1006 --error_message_count
;
1007 error_at_line (0, 0, header
->pos
.file_name
, header
->pos
.line_number
,
1008 _("...but header entry lacks a \"plural=EXPRESSION\" attribute"));
1009 error_with_progname
= true;
1011 exit_status
= EXIT_FAILURE
;
1013 if (nplurals
== NULL
&& has_plural
!= NULL
)
1015 error_with_progname
= false;
1016 error_at_line (0, 0, has_plural
->file_name
, has_plural
->line_number
,
1017 _("message catalog has plural form translations..."));
1018 --error_message_count
;
1019 error_at_line (0, 0, header
->pos
.file_name
, header
->pos
.line_number
,
1020 _("...but header entry lacks a \"nplurals=INTEGER\" attribute"));
1021 error_with_progname
= true;
1023 exit_status
= EXIT_FAILURE
;
1025 if (plural
!= NULL
&& nplurals
!= NULL
)
1028 unsigned long int nplurals_value
;
1029 struct parse_args args
;
1030 struct expression
*plural_expr
;
1032 /* First check the number. */
1034 while (*nplurals
!= '\0' && isspace ((unsigned char) *nplurals
))
1038 if (*nplurals
>= '0' && *nplurals
<= '9')
1039 nplurals_value
= strtoul (nplurals
, (char **) &endp
, 10);
1040 if (nplurals
== endp
)
1042 error_with_progname
= false;
1043 error_at_line (0, 0,
1044 header
->pos
.file_name
, header
->pos
.line_number
,
1045 _("invalid nplurals value"));
1046 error_with_progname
= true;
1048 exit_status
= EXIT_FAILURE
;
1051 /* Then check the expression. */
1054 if (parse_plural_expression (&args
) != 0)
1056 error_with_progname
= false;
1057 error_at_line (0, 0,
1058 header
->pos
.file_name
, header
->pos
.line_number
,
1059 _("invalid plural expression"));
1060 error_with_progname
= true;
1062 exit_status
= EXIT_FAILURE
;
1064 plural_expr
= args
.res
;
1066 /* See whether nplurals and plural fit together. */
1067 if (exit_status
!= EXIT_FAILURE
)
1068 check_plural_eval (plural_expr
, nplurals_value
, &header
->pos
);
1070 /* Check the number of plurals of the translations. */
1071 if (exit_status
!= EXIT_FAILURE
)
1073 if (min_nplurals
< nplurals_value
)
1075 error_with_progname
= false;
1076 error_at_line (0, 0,
1077 header
->pos
.file_name
, header
->pos
.line_number
,
1078 _("nplurals = %lu..."), nplurals_value
);
1079 --error_message_count
;
1080 error_at_line (0, 0, min_pos
->file_name
, min_pos
->line_number
,
1081 ngettext ("...but some messages have only one plural form",
1082 "...but some messages have only %lu plural forms",
1085 error_with_progname
= true;
1086 exit_status
= EXIT_FAILURE
;
1088 else if (max_nplurals
> nplurals_value
)
1090 error_with_progname
= false;
1091 error_at_line (0, 0,
1092 header
->pos
.file_name
, header
->pos
.line_number
,
1093 _("nplurals = %lu..."), nplurals_value
);
1094 --error_message_count
;
1095 error_at_line (0, 0, max_pos
->file_name
, max_pos
->line_number
,
1096 ngettext ("...but some messages have one plural form",
1097 "...but some messages have %lu plural forms",
1100 error_with_progname
= true;
1101 exit_status
= EXIT_FAILURE
;
1103 /* The only valid case is max_nplurals <= n <= min_nplurals,
1104 which means either has_plural == NULL or
1105 max_nplurals = n = min_nplurals. */
1108 /* Try to help the translator by looking up the right plural formula
1112 const char *language
;
1114 language
= strstr (nullentry
, "Language-Team: ");
1115 if (language
!= NULL
)
1118 for (j
= 0; j
< plural_table_size
; j
++)
1119 if (strncmp (language
,
1120 plural_table
[j
].language
,
1121 strlen (plural_table
[j
].language
)) == 0)
1124 xasprintf ("Plural-Forms: %s\\n", plural_table
[j
].value
);
1126 _("Try using the following, valid for %s:\n"),
1127 plural_table
[j
].language
);
1128 fprintf (stderr
, "\"%s\"\n", recommended
);
1135 else if (has_plural
!= NULL
)
1137 error_with_progname
= false;
1138 error_at_line (0, 0, has_plural
->file_name
, has_plural
->line_number
,
1139 _("message catalog has plural form translations, but lacks a header entry with \"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\""));
1140 error_with_progname
= true;
1141 exit_status
= EXIT_FAILURE
;
1146 /* Signal an error when checking format strings. */
1147 static lex_pos_ty curr_msgid_pos
;
1149 formatstring_error_logger (const char *format
, ...)
1153 va_start (args
, format
);
1154 fprintf (stderr
, "%s:%lu: ",
1155 curr_msgid_pos
.file_name
,
1156 (unsigned long) curr_msgid_pos
.line_number
);
1157 vfprintf (stderr
, format
, args
);
1158 putc ('\n', stderr
);
1161 ++error_message_count
;
1165 /* Perform miscellaneous checks on a message. */
1167 check_pair (const char *msgid
,
1168 const lex_pos_ty
*msgid_pos
,
1169 const char *msgid_plural
,
1170 const char *msgstr
, size_t msgstr_len
,
1171 const lex_pos_ty
*msgstr_pos
, enum is_format is_format
[NFORMATS
])
1177 /* If the msgid string is empty we have the special entry reserved for
1178 information about the translation. */
1179 if (msgid
[0] == '\0')
1182 /* Test 1: check whether all or none of the strings begin with a '\n'. */
1183 has_newline
= (msgid
[0] == '\n');
1184 #define TEST_NEWLINE(p) (p[0] == '\n')
1185 if (msgid_plural
!= NULL
)
1187 if (TEST_NEWLINE(msgid_plural
) != has_newline
)
1189 error_with_progname
= false;
1190 error_at_line (0, 0, msgid_pos
->file_name
, msgid_pos
->line_number
,
1192 `msgid' and `msgid_plural' entries do not both begin with '\\n'"));
1193 error_with_progname
= true;
1194 exit_status
= EXIT_FAILURE
;
1196 for (p
= msgstr
, j
= 0; p
< msgstr
+ msgstr_len
; p
+= strlen (p
) + 1, j
++)
1197 if (TEST_NEWLINE(p
) != has_newline
)
1199 error_with_progname
= false;
1200 error_at_line (0, 0, msgid_pos
->file_name
, msgid_pos
->line_number
,
1202 `msgid' and `msgstr[%u]' entries do not both begin with '\\n'"), j
);
1203 error_with_progname
= true;
1204 exit_status
= EXIT_FAILURE
;
1209 if (TEST_NEWLINE(msgstr
) != has_newline
)
1211 error_with_progname
= false;
1212 error_at_line (0, 0, msgid_pos
->file_name
, msgid_pos
->line_number
,
1214 `msgid' and `msgstr' entries do not both begin with '\\n'"));
1215 error_with_progname
= true;
1216 exit_status
= EXIT_FAILURE
;
1221 /* Test 2: check whether all or none of the strings end with a '\n'. */
1222 has_newline
= (msgid
[strlen (msgid
) - 1] == '\n');
1223 #define TEST_NEWLINE(p) (p[0] != '\0' && p[strlen (p) - 1] == '\n')
1224 if (msgid_plural
!= NULL
)
1226 if (TEST_NEWLINE(msgid_plural
) != has_newline
)
1228 error_with_progname
= false;
1229 error_at_line (0, 0, msgid_pos
->file_name
, msgid_pos
->line_number
,
1231 `msgid' and `msgid_plural' entries do not both end with '\\n'"));
1232 error_with_progname
= true;
1233 exit_status
= EXIT_FAILURE
;
1235 for (p
= msgstr
, j
= 0; p
< msgstr
+ msgstr_len
; p
+= strlen (p
) + 1, j
++)
1236 if (TEST_NEWLINE(p
) != has_newline
)
1238 error_with_progname
= false;
1239 error_at_line (0, 0, msgid_pos
->file_name
, msgid_pos
->line_number
,
1241 `msgid' and `msgstr[%u]' entries do not both end with '\\n'"), j
);
1242 error_with_progname
= true;
1243 exit_status
= EXIT_FAILURE
;
1248 if (TEST_NEWLINE(msgstr
) != has_newline
)
1250 error_with_progname
= false;
1251 error_at_line (0, 0, msgid_pos
->file_name
, msgid_pos
->line_number
,
1253 `msgid' and `msgstr' entries do not both end with '\\n'"));
1254 error_with_progname
= true;
1255 exit_status
= EXIT_FAILURE
;
1260 if (check_compatibility
&& msgid_plural
!= NULL
)
1262 error_with_progname
= false;
1263 error_at_line (0, 0, msgid_pos
->file_name
, msgid_pos
->line_number
,
1264 _("plural handling is a GNU gettext extension"));
1265 error_with_progname
= true;
1266 exit_status
= EXIT_FAILURE
;
1269 if (check_format_strings
)
1270 /* Test 3: Check whether both formats strings contain the same number
1271 of format specifications. */
1273 curr_msgid_pos
= *msgid_pos
;
1274 if (check_msgid_msgstr_format (msgid
, msgid_plural
, msgstr
, msgstr_len
,
1275 is_format
, formatstring_error_logger
))
1276 exit_status
= EXIT_FAILURE
;
1279 if (check_accelerators
&& msgid_plural
== NULL
)
1280 /* Test 4: Check that if msgid is a menu item with a keyboard accelerator,
1281 the msgstr has an accelerator as well. A keyboard accelerator is
1282 designated by an immediately preceding '&'. We cannot check whether
1283 two accelerators collide, only whether the translator has bothered
1284 thinking about them. */
1288 /* We are only interested in msgids that contain exactly one '&'. */
1289 p
= strchr (msgid
, accelerator_char
);
1290 if (p
!= NULL
&& strchr (p
+ 1, accelerator_char
) == NULL
)
1292 /* Count the number of '&' in msgstr, but ignore '&&'. */
1293 unsigned int count
= 0;
1295 for (p
= msgstr
; (p
= strchr (p
, accelerator_char
)) != NULL
; p
++)
1296 if (p
[1] == accelerator_char
)
1303 error_with_progname
= false;
1304 error_at_line (0, 0, msgid_pos
->file_name
, msgid_pos
->line_number
,
1305 _("msgstr lacks the keyboard accelerator mark '%c'"),
1307 error_with_progname
= true;
1311 error_with_progname
= false;
1312 error_at_line (0, 0, msgid_pos
->file_name
, msgid_pos
->line_number
,
1313 _("msgstr has too many keyboard accelerator marks '%c'"),
1315 error_with_progname
= true;
1322 /* Perform miscellaneous checks on a header entry. */
1324 check_header_entry (const char *msgstr_string
)
1326 static const char *required_fields
[] =
1328 "Project-Id-Version", "PO-Revision-Date", "Last-Translator",
1329 "Language-Team", "MIME-Version", "Content-Type",
1330 "Content-Transfer-Encoding"
1332 static const char *default_values
[] =
1334 "PACKAGE VERSION", "YEAR-MO-DA", "FULL NAME", "LANGUAGE", NULL
,
1335 "text/plain; charset=CHARSET", "ENCODING"
1337 const size_t nfields
= SIZEOF (required_fields
);
1341 for (cnt
= 0; cnt
< nfields
; ++cnt
)
1343 char *endp
= strstr (msgstr_string
, required_fields
[cnt
]);
1346 multiline_error (xasprintf ("%s: ", gram_pos
.file_name
),
1347 xasprintf (_("headerfield `%s' missing in header\n"),
1348 required_fields
[cnt
]));
1349 else if (endp
!= msgstr_string
&& endp
[-1] != '\n')
1350 multiline_error (xasprintf ("%s: ", gram_pos
.file_name
),
1352 header field `%s' should start at beginning of line\n"),
1353 required_fields
[cnt
]));
1354 else if (default_values
[cnt
] != NULL
1355 && strncmp (default_values
[cnt
],
1356 endp
+ strlen (required_fields
[cnt
]) + 2,
1357 strlen (default_values
[cnt
])) == 0)
1361 multiline_error (xasprintf ("%s: ", gram_pos
.file_name
),
1363 some header fields still have the initial default value\n")));
1373 multiline_error (xasprintf ("%s: ", gram_pos
.file_name
),
1375 field `%s' still has initial default value\n"),
1376 required_fields
[initial
]));
1380 /* The rest of the file defines a subclass msgfmt_po_reader_ty of
1381 default_po_reader_ty. Its particularities are:
1382 - The header entry check is performed on-the-fly.
1383 - Comments are not stored, they are discarded right away.
1384 (This is achieved by setting handle_comments = false and
1385 handle_filepos_comments = false.)
1386 - The multi-domain handling is adapted to our domain_list.
1390 /* This structure defines a derived class of the default_po_reader_ty class.
1391 (See read-po-abstract.h for an explanation.) */
1392 typedef struct msgfmt_po_reader_ty msgfmt_po_reader_ty
;
1393 struct msgfmt_po_reader_ty
1395 /* inherited instance variables, etc */
1396 DEFAULT_PO_READER_TY
1398 bool has_header_entry
;
1399 bool has_nonfuzzy_header_entry
;
1403 /* Prepare for first message. */
1405 msgfmt_constructor (abstract_po_reader_ty
*that
)
1407 msgfmt_po_reader_ty
*this = (msgfmt_po_reader_ty
*) that
;
1409 /* Invoke superclass constructor. */
1410 default_constructor (that
);
1412 this->has_header_entry
= false;
1413 this->has_nonfuzzy_header_entry
= false;
1417 /* Some checks after whole file is read. */
1419 msgfmt_parse_debrief (abstract_po_reader_ty
*that
)
1421 msgfmt_po_reader_ty
*this = (msgfmt_po_reader_ty
*) that
;
1423 /* Invoke superclass method. */
1424 default_parse_debrief (that
);
1426 /* Test whether header entry was found. */
1429 if (!this->has_header_entry
)
1431 multiline_error (xasprintf ("%s: ", gram_pos
.file_name
),
1433 warning: PO file header missing or invalid\n")));
1434 multiline_error (NULL
,
1436 warning: charset conversion will not work\n")));
1438 else if (!this->has_nonfuzzy_header_entry
)
1440 /* Has only a fuzzy header entry. Since the versions 0.10.xx
1441 ignore a fuzzy header entry and even give an error on it, we
1442 give a warning, to increase operability with these older
1443 msgfmt versions. This warning can go away in January 2003. */
1444 multiline_warning (xasprintf ("%s: ", gram_pos
.file_name
),
1445 xasprintf (_("warning: PO file header fuzzy\n")));
1446 multiline_warning (NULL
,
1448 warning: older versions of msgfmt will give an error on this\n")));
1454 /* Set 'domain' directive when seen in .po file. */
1456 msgfmt_set_domain (default_po_reader_ty
*this, char *name
)
1458 /* If no output file was given, we change it with each `domain'
1460 if (!java_mode
&& !csharp_mode
&& !csharp_resources_mode
&& !tcl_mode
1461 && !qt_mode
&& output_file_name
== NULL
)
1465 correct
= strcspn (name
, INVALID_PATH_CHAR
);
1466 if (name
[correct
] != '\0')
1468 exit_status
= EXIT_FAILURE
;
1472 domain name \"%s\" not suitable as file name"), name
);
1477 domain name \"%s\" not suitable as file name: will use prefix"), name
);
1478 name
[correct
] = '\0';
1481 /* Set new domain. */
1482 current_domain
= new_domain (name
, add_mo_suffix (name
));
1483 this->domain
= current_domain
->domain_name
;
1484 this->mlp
= current_domain
->mlp
;
1489 po_gram_error_at_line (&gram_pos
,
1490 _("`domain %s' directive ignored"), name
);
1492 /* NAME was allocated in po-gram-gen.y but is not used anywhere. */
1499 msgfmt_add_message (default_po_reader_ty
*this,
1501 lex_pos_ty
*msgid_pos
,
1503 char *msgstr
, size_t msgstr_len
,
1504 lex_pos_ty
*msgstr_pos
,
1505 bool force_fuzzy
, bool obsolete
)
1507 /* Check whether already a domain is specified. If not, use default
1509 if (current_domain
== NULL
)
1511 current_domain
= new_domain (MESSAGE_DOMAIN_DEFAULT
,
1512 add_mo_suffix (MESSAGE_DOMAIN_DEFAULT
));
1513 /* Keep current_domain and this->domain synchronized. */
1514 this->domain
= current_domain
->domain_name
;
1515 this->mlp
= current_domain
->mlp
;
1518 /* Invoke superclass method. */
1519 default_add_message (this, msgid
, msgid_pos
, msgid_plural
,
1520 msgstr
, msgstr_len
, msgstr_pos
, force_fuzzy
, obsolete
);
1525 msgfmt_frob_new_message (default_po_reader_ty
*that
, message_ty
*mp
,
1526 const lex_pos_ty
*msgid_pos
,
1527 const lex_pos_ty
*msgstr_pos
)
1529 msgfmt_po_reader_ty
*this = (msgfmt_po_reader_ty
*) that
;
1533 /* Don't emit untranslated entries.
1534 Also don't emit fuzzy entries, unless --use-fuzzy was specified.
1535 But ignore fuzziness of the header entry. */
1536 if (mp
->msgstr
[0] == '\0'
1537 || (!include_all
&& mp
->is_fuzzy
&& mp
->msgid
[0] != '\0'))
1539 if (check_compatibility
)
1541 error_with_progname
= false;
1542 error_at_line (0, 0, mp
->pos
.file_name
, mp
->pos
.line_number
,
1543 (mp
->msgstr
[0] == '\0'
1544 ? _("empty `msgstr' entry ignored")
1545 : _("fuzzy `msgstr' entry ignored")));
1546 error_with_progname
= true;
1549 /* Increment counter for fuzzy/untranslated messages. */
1550 if (mp
->msgstr
[0] == '\0')
1551 ++msgs_untranslated
;
1555 mp
->obsolete
= true;
1559 /* Test for header entry. */
1560 if (mp
->msgid
[0] == '\0')
1562 this->has_header_entry
= true;
1564 this->has_nonfuzzy_header_entry
= true;
1566 /* Do some more tests on the contents of the header entry. */
1568 check_header_entry (mp
->msgstr
);
1571 /* We don't count the header entry in the statistic so place
1572 the counter incrementation here. */
1578 /* Do some more checks on both strings. */
1579 check_pair (mp
->msgid
, msgid_pos
, mp
->msgid_plural
,
1580 mp
->msgstr
, mp
->msgstr_len
, msgstr_pos
,
1587 /* Test for `#, fuzzy' comments and warn. */
1589 msgfmt_comment_special (abstract_po_reader_ty
*that
, const char *s
)
1591 msgfmt_po_reader_ty
*this = (msgfmt_po_reader_ty
*) that
;
1593 /* Invoke superclass method. */
1594 default_comment_special (that
, s
);
1598 static bool warned
= false;
1600 if (!include_all
&& check_compatibility
&& !warned
)
1604 %s: warning: source file contains fuzzy translation"),
1605 gram_pos
.file_name
);
1611 /* So that the one parser can be used for multiple programs, and also
1612 use good data hiding and encapsulation practices, an object
1613 oriented approach has been taken. An object instance is allocated,
1614 and all actions resulting from the parse will be through
1615 invocations of method functions of that object. */
1617 static default_po_reader_class_ty msgfmt_methods
=
1620 sizeof (msgfmt_po_reader_ty
),
1623 default_parse_brief
,
1624 msgfmt_parse_debrief
,
1625 default_directive_domain
,
1626 default_directive_message
,
1628 default_comment_dot
,
1629 default_comment_filepos
,
1630 msgfmt_comment_special
1632 msgfmt_set_domain
, /* set_domain */
1633 msgfmt_add_message
, /* add_message */
1634 msgfmt_frob_new_message
/* frob_new_message */
1638 /* Read .po file FILENAME and store translation pairs. */
1640 read_po_file_msgfmt (char *filename
)
1642 char *real_filename
;
1643 FILE *fp
= open_po_file (filename
, &real_filename
, true);
1644 default_po_reader_ty
*pop
;
1646 pop
= default_po_reader_alloc (&msgfmt_methods
);
1647 pop
->handle_comments
= false;
1648 pop
->handle_filepos_comments
= false;
1649 pop
->allow_domain_directives
= true;
1650 pop
->allow_duplicates
= false;
1651 pop
->allow_duplicates_if_same_msgstr
= false;
1654 if (current_domain
!= NULL
)
1656 /* Keep current_domain and this->domain synchronized. */
1657 pop
->domain
= current_domain
->domain_name
;
1658 pop
->mlp
= current_domain
->mlp
;
1660 po_lex_pass_obsolete_entries (true);
1661 po_scan ((abstract_po_reader_ty
*) pop
, fp
, real_filename
, filename
,
1663 po_reader_free ((abstract_po_reader_ty
*) pop
);