1 /* Initializes a new PO file.
2 Copyright (C) 2001-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 #include <alloca.h>
23
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <getopt.h>
27 #include <limits.h>
28 #include <locale.h>
29 #include <stdint.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <time.h>
34 #include <sys/types.h>
35
36 #if HAVE_PWD_H
37 # include <pwd.h>
38 #endif
39
40 #include <unistd.h>
41
42 #if HAVE_DIRENT_H
43 # include <dirent.h>
44 #endif
45
46 #if HAVE_DIRENT_H
47 # define HAVE_DIR 1
48 #else
49 # define HAVE_DIR 0
50 #endif
51
52 #include <textstyle.h>
53
54 /* Get BINDIR. */
55 #include "configmake.h"
56
57 #include "noreturn.h"
58 #include "closeout.h"
59 #include "error.h"
60 #include "error-progname.h"
61 #include "progname.h"
62 #include "relocatable.h"
63 #include "basename-lgpl.h"
64 #include "c-strstr.h"
65 #include "c-strcase.h"
66 #include "message.h"
67 #include "read-catalog.h"
68 #include "read-po.h"
69 #include "read-properties.h"
70 #include "read-stringtable.h"
71 #include "write-catalog.h"
72 #include "write-po.h"
73 #include "write-properties.h"
74 #include "write-stringtable.h"
75 #include "po-charset.h"
76 #include "localcharset.h"
77 #include "localename.h"
78 #include "po-time.h"
79 #include "plural-table.h"
80 #include "lang-table.h"
81 #include "xalloc.h"
82 #include "xmalloca.h"
83 #include "concat-filename.h"
84 #include "xerror.h"
85 #include "xvasprintf.h"
86 #include "msgl-english.h"
87 #include "plural-count.h"
88 #include "spawn-pipe.h"
89 #include "wait-process.h"
90 #include "xsetenv.h"
91 #include "str-list.h"
92 #include "propername.h"
93 #include "gettext.h"
94
95 #define _(str) gettext (str)
96 #define N_(str) (str)
97
98 /* Get F_OK. It is lacking from <fcntl.h> on Woe32. */
99 #ifndef F_OK
100 # define F_OK 0
101 #endif
102
103 #define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
104
105 extern const char * _nl_expand_alias (const char *name);
106
107 /* Locale name. */
108 static const char *locale;
109
110 /* Language (ISO-639 code) and optional territory (ISO-3166 code). */
111 static const char *catalogname;
112
113 /* Language (ISO-639 code). */
114 static const char *language;
115
116 /* If true, the user is not considered to be the translator. */
117 static bool no_translator;
118
119 /* Long options. */
120 static const struct option long_options[] =
121 {
122 { "color", optional_argument, NULL, CHAR_MAX + 5 },
123 { "help", no_argument, NULL, 'h' },
124 { "input", required_argument, NULL, 'i' },
125 { "locale", required_argument, NULL, 'l' },
126 { "no-translator", no_argument, NULL, CHAR_MAX + 1 },
127 { "no-wrap", no_argument, NULL, CHAR_MAX + 2 },
128 { "output-file", required_argument, NULL, 'o' },
129 { "properties-input", no_argument, NULL, 'P' },
130 { "properties-output", no_argument, NULL, 'p' },
131 { "stringtable-input", no_argument, NULL, CHAR_MAX + 3 },
132 { "stringtable-output", no_argument, NULL, CHAR_MAX + 4 },
133 { "style", required_argument, NULL, CHAR_MAX + 6 },
134 { "version", no_argument, NULL, 'V' },
135 { "width", required_argument, NULL, 'w' },
136 { NULL, 0, NULL, 0 }
137 };
138
139 /* Forward declaration of local functions. */
140 _GL_NORETURN_FUNC static void usage (int status);
141 static const char *find_pot (void);
142 static const char *catalogname_for_locale (const char *locale);
143 static const char *language_of_locale (const char *locale);
144 static char *get_field (const char *header, const char *field);
145 static msgdomain_list_ty *fill_header (msgdomain_list_ty *mdlp);
146 static msgdomain_list_ty *update_msgstr_plurals (msgdomain_list_ty *mdlp);
147
148
149 int
main(int argc,char ** argv)150 main (int argc, char **argv)
151 {
152 int opt;
153 bool do_help;
154 bool do_version;
155 char *output_file;
156 const char *input_file;
157 msgdomain_list_ty *result;
158 catalog_input_format_ty input_syntax = &input_format_po;
159 catalog_output_format_ty output_syntax = &output_format_po;
160
161 /* Set program name for messages. */
162 set_program_name (argv[0]);
163 error_print_progname = maybe_print_progname;
164
165 /* Set locale via LC_ALL. */
166 setlocale (LC_ALL, "");
167
168 /* Set the text message domain. */
169 bindtextdomain (PACKAGE, relocate (LOCALEDIR));
170 bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
171 textdomain (PACKAGE);
172
173 /* Ensure that write errors on stdout are detected. */
174 atexit (close_stdout);
175
176 /* Set default values for variables. */
177 do_help = false;
178 do_version = false;
179 output_file = NULL;
180 input_file = NULL;
181 locale = NULL;
182
183 while ((opt = getopt_long (argc, argv, "hi:l:o:pPVw:", long_options, NULL))
184 != EOF)
185 switch (opt)
186 {
187 case '\0': /* Long option. */
188 break;
189
190 case 'h':
191 do_help = true;
192 break;
193
194 case 'i':
195 if (input_file != NULL)
196 {
197 error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
198 usage (EXIT_FAILURE);
199 }
200 input_file = optarg;
201 break;
202
203 case 'l':
204 locale = optarg;
205 break;
206
207 case 'o':
208 output_file = optarg;
209 break;
210
211 case 'p':
212 output_syntax = &output_format_properties;
213 break;
214
215 case 'P':
216 input_syntax = &input_format_properties;
217 break;
218
219 case 'V':
220 do_version = true;
221 break;
222
223 case 'w':
224 {
225 int value;
226 char *endp;
227 value = strtol (optarg, &endp, 10);
228 if (endp != optarg)
229 message_page_width_set (value);
230 }
231 break;
232
233 case CHAR_MAX + 1:
234 no_translator = true;
235 break;
236
237 case CHAR_MAX + 2: /* --no-wrap */
238 message_page_width_ignore ();
239 break;
240
241 case CHAR_MAX + 3: /* --stringtable-input */
242 input_syntax = &input_format_stringtable;
243 break;
244
245 case CHAR_MAX + 4: /* --stringtable-output */
246 output_syntax = &output_format_stringtable;
247 break;
248
249 case CHAR_MAX + 5: /* --color */
250 if (handle_color_option (optarg) || color_test_mode)
251 usage (EXIT_FAILURE);
252 break;
253
254 case CHAR_MAX + 6: /* --style */
255 handle_style_option (optarg);
256 break;
257
258 default:
259 usage (EXIT_FAILURE);
260 break;
261 }
262
263 /* Version information is requested. */
264 if (do_version)
265 {
266 printf ("%s (GNU %s) %s\n", last_component (program_name),
267 PACKAGE, VERSION);
268 /* xgettext: no-wrap */
269 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
270 License GPLv3+: GNU GPL version 3 or later <%s>\n\
271 This is free software: you are free to change and redistribute it.\n\
272 There is NO WARRANTY, to the extent permitted by law.\n\
273 "),
274 "2001-2020", "https://gnu.org/licenses/gpl.html");
275 printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
276 exit (EXIT_SUCCESS);
277 }
278
279 /* Help is requested. */
280 if (do_help)
281 usage (EXIT_SUCCESS);
282
283 /* Test for extraneous arguments. */
284 if (optind != argc)
285 error (EXIT_FAILURE, 0, _("too many arguments"));
286
287 /* Search for the input file. */
288 if (input_file == NULL)
289 input_file = find_pot ();
290
291 /* Determine target locale. */
292 if (locale == NULL)
293 {
294 locale = gl_locale_name (LC_MESSAGES, "LC_MESSAGES");
295 if (strcmp (locale, "C") == 0)
296 {
297 const char *doc_url =
298 "https://www.gnu.org/software/gettext/manual/html_node/Setting-the-POSIX-Locale.html";
299 multiline_error (xstrdup (""),
300 xasprintf (_("\
301 You are in a language indifferent environment. Please set\n\
302 your LANG environment variable, as described in\n\
303 <%s>.\n\
304 This is necessary so you can test your translations.\n"),
305 doc_url));
306 exit (EXIT_FAILURE);
307 }
308 }
309 {
310 const char *alias = _nl_expand_alias (locale);
311 if (alias != NULL)
312 locale = alias;
313 }
314 catalogname = catalogname_for_locale (locale);
315 language = language_of_locale (locale);
316
317 /* Default output file name is CATALOGNAME.po. */
318 if (output_file == NULL)
319 {
320 output_file = xasprintf ("%s.po", catalogname);
321
322 /* But don't overwrite existing PO files. */
323 if (access (output_file, F_OK) == 0)
324 {
325 multiline_error (xstrdup (""),
326 xasprintf (_("\
327 Output file %s already exists.\n\
328 Please specify the locale through the --locale option or\n\
329 the output .po file through the --output-file option.\n"),
330 output_file));
331 exit (EXIT_FAILURE);
332 }
333 }
334
335 /* Read input file. */
336 result = read_catalog_file (input_file, input_syntax);
337
338 #if defined _WIN32 || defined __CYGWIN__
339 /* The function fill_header invokes, directly or indirectly, some programs
340 that are installed in ${libdir}/gettext:
341 - hostname, invoked indirectly through 'user-email'.
342 - urlget, invoked indirectly through 'team-address'.
343 - cldr-plurals, invoked directly.
344 These programs depend on libintl. In installations with shared libraries,
345 we need to guarantee that the programs find the DLL, which is installed
346 in ${bindir}, not in ${libdir}/gettext. The preferred way to do so is to
347 extend $PATH, so that it contains ${bindir}. */
348 {
349 const char *orig_path;
350 size_t orig_path_len;
351 char separator;
352 const char *bindir;
353 size_t bindir_len;
354 char *augmented_path;
355
356 orig_path = getenv ("PATH");
357 if (orig_path == NULL)
358 orig_path = "";
359 orig_path_len = strlen (orig_path);
360
361 #if defined __CYGWIN__
362 separator = ':';
363 #else /* native Windows */
364 separator = ';';
365 #endif
366
367 bindir = BINDIR;
368 bindir_len = strlen (bindir);
369
370 /* Concatenate bindir, separator, orig_path. */
371 augmented_path = XNMALLOC (bindir_len + 1 + orig_path_len + 1, char);
372 memcpy (augmented_path, bindir, bindir_len);
373 augmented_path[bindir_len] = separator;
374 memcpy (augmented_path + bindir_len + 1, orig_path, orig_path_len + 1);
375
376 xsetenv ("PATH", augmented_path, 1);
377 }
378 #endif
379
380 /* Fill the header entry. */
381 result = fill_header (result);
382
383 /* Initialize translations. */
384 if (strcmp (language, "en") == 0)
385 result = msgdomain_list_english (result);
386 else
387 result = update_msgstr_plurals (result);
388
389 /* Write the modified message list out. */
390 msgdomain_list_print (result, output_file, output_syntax, true, false);
391
392 if (!no_translator)
393 fprintf (stderr, "\n");
394 fprintf (stderr, _("Created %s.\n"), output_file);
395
396 exit (EXIT_SUCCESS);
397 }
398
399
400 /* Display usage information and exit. */
401 static void
usage(int status)402 usage (int status)
403 {
404 if (status != EXIT_SUCCESS)
405 fprintf (stderr, _("Try '%s --help' for more information.\n"),
406 program_name);
407 else
408 {
409 printf (_("\
410 Usage: %s [OPTION]\n\
411 "), program_name);
412 printf ("\n");
413 /* xgettext: no-wrap */
414 printf (_("\
415 Creates a new PO file, initializing the meta information with values from the\n\
416 user's environment.\n\
417 "));
418 printf ("\n");
419 printf (_("\
420 Mandatory arguments to long options are mandatory for short options too.\n"));
421 printf ("\n");
422 printf (_("\
423 Input file location:\n"));
424 printf (_("\
425 -i, --input=INPUTFILE input POT file\n"));
426 printf (_("\
427 If no input file is given, the current directory is searched for the POT file.\n\
428 If it is -, standard input is read.\n"));
429 printf ("\n");
430 printf (_("\
431 Output file location:\n"));
432 printf (_("\
433 -o, --output-file=FILE write output to specified PO file\n"));
434 printf (_("\
435 If no output file is given, it depends on the --locale option or the user's\n\
436 locale setting. If it is -, the results are written to standard output.\n"));
437 printf ("\n");
438 printf (_("\
439 Input file syntax:\n"));
440 printf (_("\
441 -P, --properties-input input file is in Java .properties syntax\n"));
442 printf (_("\
443 --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n"));
444 printf ("\n");
445 printf (_("\
446 Output details:\n"));
447 printf (_("\
448 -l, --locale=LL_CC[.ENCODING] set target locale\n"));
449 printf (_("\
450 --no-translator assume the PO file is automatically generated\n"));
451 printf (_("\
452 --color use colors and other text attributes always\n\
453 --color=WHEN use colors and other text attributes if WHEN.\n\
454 WHEN may be 'always', 'never', 'auto', or 'html'.\n"));
455 printf (_("\
456 --style=STYLEFILE specify CSS style rule file for --color\n"));
457 printf (_("\
458 -p, --properties-output write out a Java .properties file\n"));
459 printf (_("\
460 --stringtable-output write out a NeXTstep/GNUstep .strings file\n"));
461 printf (_("\
462 -w, --width=NUMBER set output page width\n"));
463 printf (_("\
464 --no-wrap do not break long message lines, longer than\n\
465 the output page width, into several lines\n"));
466 printf ("\n");
467 printf (_("\
468 Informative output:\n"));
469 printf (_("\
470 -h, --help display this help and exit\n"));
471 printf (_("\
472 -V, --version output version information and exit\n"));
473 printf ("\n");
474 /* TRANSLATORS: The first placeholder is the web address of the Savannah
475 project of this package. The second placeholder is the bug-reporting
476 email address for this package. Please add _another line_ saying
477 "Report translation bugs to <...>\n" with the address for translation
478 bugs (typically your translation team's web or email address). */
479 printf(_("\
480 Report bugs in the bug tracker at <%s>\n\
481 or by email to <%s>.\n"),
482 "https://savannah.gnu.org/projects/gettext",
483 "bug-gettext@gnu.org");
484 }
485
486 exit (status);
487 }
488
489
490 /* Search for the POT file and return its name. */
491 static const char *
find_pot()492 find_pot ()
493 {
494 #if HAVE_DIR
495 DIR *dirp;
496 char *found = NULL;
497
498 dirp = opendir (".");
499 if (dirp != NULL)
500 {
501 for (;;)
502 {
503 struct dirent *dp;
504
505 errno = 0;
506 dp = readdir (dirp);
507 if (dp != NULL)
508 {
509 const char *name = dp->d_name;
510 size_t namlen = strlen (name);
511
512 if (namlen > 4 && memcmp (name + namlen - 4, ".pot", 4) == 0)
513 {
514 if (found == NULL)
515 found = xstrdup (name);
516 else
517 {
518 multiline_error (xstrdup (""),
519 xstrdup (_("\
520 Found more than one .pot file.\n\
521 Please specify the input .pot file through the --input option.\n")));
522 usage (EXIT_FAILURE);
523 }
524 }
525 }
526 else if (errno != 0)
527 error (EXIT_FAILURE, errno, _("error reading current directory"));
528 else
529 break;
530 }
531 if (closedir (dirp))
532 error (EXIT_FAILURE, errno, _("error reading current directory"));
533
534 if (found != NULL)
535 return found;
536 }
537 #endif
538
539 multiline_error (xstrdup (""),
540 xstrdup (_("\
541 Found no .pot file in the current directory.\n\
542 Please specify the input .pot file through the --input option.\n")));
543 usage (EXIT_FAILURE);
544 /* NOTREACHED */
545 return NULL;
546 }
547
548
549 /* Return the gettext catalog name corresponding to a locale. If the locale
550 consists of a language and a territory, and the language is mainly spoken
551 in that territory, the territory is removed from the locale name.
552 For example, "de_DE" or "de_DE.ISO-8859-1" are simplified to "de",
553 because the resulting catalog can be used as a default for all "de_XX",
554 such as "de_AT". */
555 static const char *
catalogname_for_locale(const char * locale)556 catalogname_for_locale (const char *locale)
557 {
558 static const char *locales_with_principal_territory[] = {
559 /* Language Main territory */
560 "ace_ID", /* Achinese Indonesia */
561 "af_ZA", /* Afrikaans South Africa */
562 "ak_GH", /* Akan Ghana */
563 "am_ET", /* Amharic Ethiopia */
564 "an_ES", /* Aragonese Spain */
565 "ang_GB", /* Old English Britain */
566 "arn_CL", /* Mapudungun Chile */
567 "as_IN", /* Assamese India */
568 "ast_ES", /* Asturian Spain */
569 "av_RU", /* Avaric Russia */
570 "awa_IN", /* Awadhi India */
571 "az_AZ", /* Azerbaijani Azerbaijan */
572 "ban_ID", /* Balinese Indonesia */
573 "be_BY", /* Belarusian Belarus */
574 "bej_SD", /* Beja Sudan */
575 "bem_ZM", /* Bemba Zambia */
576 "bg_BG", /* Bulgarian Bulgaria */
577 "bho_IN", /* Bhojpuri India */
578 "bi_VU", /* Bislama Vanuatu */
579 "bik_PH", /* Bikol Philippines */
580 "bin_NG", /* Bini Nigeria */
581 "bm_ML", /* Bambara Mali */
582 "bn_IN", /* Bengali India */
583 "bo_CN", /* Tibetan China */
584 "br_FR", /* Breton France */
585 "bs_BA", /* Bosnian Bosnia */
586 "bug_ID", /* Buginese Indonesia */
587 "ca_ES", /* Catalan Spain */
588 "ce_RU", /* Chechen Russia */
589 "ceb_PH", /* Cebuano Philippines */
590 "co_FR", /* Corsican France */
591 "cr_CA", /* Cree Canada */
592 /* Don't put "crh_UZ" or "crh_UA" here. That would be asking for fruitless
593 political discussion. */
594 "cs_CZ", /* Czech Czech Republic */
595 "csb_PL", /* Kashubian Poland */
596 "cy_GB", /* Welsh Britain */
597 "da_DK", /* Danish Denmark */
598 "de_DE", /* German Germany */
599 "din_SD", /* Dinka Sudan */
600 "doi_IN", /* Dogri India */
601 "dsb_DE", /* Lower Sorbian Germany */
602 "dv_MV", /* Divehi Maldives */
603 "dz_BT", /* Dzongkha Bhutan */
604 "ee_GH", /* Éwé Ghana */
605 "el_GR", /* Greek Greece */
606 /* Don't put "en_GB" or "en_US" here. That would be asking for fruitless
607 political discussion. */
608 "es_ES", /* Spanish Spain */
609 "et_EE", /* Estonian Estonia */
610 "fa_IR", /* Persian Iran */
611 "fi_FI", /* Finnish Finland */
612 "fil_PH", /* Filipino Philippines */
613 "fj_FJ", /* Fijian Fiji */
614 "fo_FO", /* Faroese Faeroe Islands */
615 "fon_BJ", /* Fon Benin */
616 "fr_FR", /* French France */
617 "fur_IT", /* Friulian Italy */
618 "fy_NL", /* Western Frisian Netherlands */
619 "ga_IE", /* Irish Ireland */
620 "gd_GB", /* Scottish Gaelic Britain */
621 "gon_IN", /* Gondi India */
622 "gsw_CH", /* Swiss German Switzerland */
623 "gu_IN", /* Gujarati India */
624 "he_IL", /* Hebrew Israel */
625 "hi_IN", /* Hindi India */
626 "hil_PH", /* Hiligaynon Philippines */
627 "hr_HR", /* Croatian Croatia */
628 "hsb_DE", /* Upper Sorbian Germany */
629 "ht_HT", /* Haitian Haiti */
630 "hu_HU", /* Hungarian Hungary */
631 "hy_AM", /* Armenian Armenia */
632 "id_ID", /* Indonesian Indonesia */
633 "ig_NG", /* Igbo Nigeria */
634 "ii_CN", /* Sichuan Yi China */
635 "ilo_PH", /* Iloko Philippines */
636 "is_IS", /* Icelandic Iceland */
637 "it_IT", /* Italian Italy */
638 "ja_JP", /* Japanese Japan */
639 "jab_NG", /* Hyam Nigeria */
640 "jv_ID", /* Javanese Indonesia */
641 "ka_GE", /* Georgian Georgia */
642 "kab_DZ", /* Kabyle Algeria */
643 "kaj_NG", /* Jju Nigeria */
644 "kam_KE", /* Kamba Kenya */
645 "kmb_AO", /* Kimbundu Angola */
646 "kcg_NG", /* Tyap Nigeria */
647 "kdm_NG", /* Kagoma Nigeria */
648 "kg_CD", /* Kongo Democratic Republic of Congo */
649 "kk_KZ", /* Kazakh Kazakhstan */
650 "kl_GL", /* Kalaallisut Greenland */
651 "km_KH", /* Central Khmer Cambodia */
652 "kn_IN", /* Kannada India */
653 "ko_KR", /* Korean Korea (South) */
654 "kok_IN", /* Konkani India */
655 "kr_NG", /* Kanuri Nigeria */
656 "kru_IN", /* Kurukh India */
657 "ky_KG", /* Kyrgyz Kyrgyzstan */
658 "lg_UG", /* Ganda Uganda */
659 "li_BE", /* Limburgish Belgium */
660 "lo_LA", /* Laotian Laos */
661 "lt_LT", /* Lithuanian Lithuania */
662 "lu_CD", /* Luba-Katanga Democratic Republic of Congo */
663 "lua_CD", /* Luba-Lulua Democratic Republic of Congo */
664 "luo_KE", /* Luo Kenya */
665 "lv_LV", /* Latvian Latvia */
666 "mad_ID", /* Madurese Indonesia */
667 "mag_IN", /* Magahi India */
668 "mai_IN", /* Maithili India */
669 "mak_ID", /* Makasar Indonesia */
670 "man_ML", /* Mandingo Mali */
671 "men_SL", /* Mende Sierra Leone */
672 "mfe_MU", /* Mauritian Creole Mauritius */
673 "mg_MG", /* Malagasy Madagascar */
674 "mi_NZ", /* Maori New Zealand */
675 "min_ID", /* Minangkabau Indonesia */
676 "mk_MK", /* Macedonian North Macedonia */
677 "ml_IN", /* Malayalam India */
678 "mn_MN", /* Mongolian Mongolia */
679 "mni_IN", /* Manipuri India */
680 "mos_BF", /* Mossi Burkina Faso */
681 "mr_IN", /* Marathi India */
682 "ms_MY", /* Malay Malaysia */
683 "mt_MT", /* Maltese Malta */
684 "mwr_IN", /* Marwari India */
685 "my_MM", /* Burmese Myanmar */
686 "na_NR", /* Nauru Nauru */
687 "nah_MX", /* Nahuatl Mexico */
688 "nap_IT", /* Neapolitan Italy */
689 "nb_NO", /* Norwegian Bokmål Norway */
690 "nds_DE", /* Low Saxon Germany */
691 "ne_NP", /* Nepali Nepal */
692 "nl_NL", /* Dutch Netherlands */
693 "nn_NO", /* Norwegian Nynorsk Norway */
694 "no_NO", /* Norwegian Norway */
695 "nr_ZA", /* South Ndebele South Africa */
696 "nso_ZA", /* Northern Sotho South Africa */
697 "ny_MW", /* Chichewa Malawi */
698 "nym_TZ", /* Nyamwezi Tanzania */
699 "nyn_UG", /* Nyankole Uganda */
700 "oc_FR", /* Occitan France */
701 "oj_CA", /* Ojibwa Canada */
702 "or_IN", /* Oriya India */
703 "pa_IN", /* Punjabi India */
704 "pag_PH", /* Pangasinan Philippines */
705 "pam_PH", /* Pampanga Philippines */
706 "pap_AN", /* Papiamento Netherlands Antilles - this line can be removed in 2018 */
707 "pbb_CO", /* Páez Colombia */
708 "pl_PL", /* Polish Poland */
709 "ps_AF", /* Pashto Afghanistan */
710 "pt_PT", /* Portuguese Portugal */
711 "raj_IN", /* Rajasthani India */
712 "rm_CH", /* Romansh Switzerland */
713 "rn_BI", /* Kirundi Burundi */
714 "ro_RO", /* Romanian Romania */
715 "ru_RU", /* Russian Russia */
716 "rw_RW", /* Kinyarwanda Rwanda */
717 "sa_IN", /* Sanskrit India */
718 "sah_RU", /* Yakut Russia */
719 "sas_ID", /* Sasak Indonesia */
720 "sat_IN", /* Santali India */
721 "sc_IT", /* Sardinian Italy */
722 "scn_IT", /* Sicilian Italy */
723 "sg_CF", /* Sango Central African Republic */
724 "shn_MM", /* Shan Myanmar */
725 "si_LK", /* Sinhala Sri Lanka */
726 "sid_ET", /* Sidamo Ethiopia */
727 "sk_SK", /* Slovak Slovakia */
728 "sl_SI", /* Slovenian Slovenia */
729 "smn_FI", /* Inari Sami Finland */
730 "sms_FI", /* Skolt Sami Finland */
731 "so_SO", /* Somali Somalia */
732 "sq_AL", /* Albanian Albania */
733 "sr_RS", /* Serbian Serbia */
734 "srr_SN", /* Serer Senegal */
735 "suk_TZ", /* Sukuma Tanzania */
736 "sus_GN", /* Susu Guinea */
737 "sv_SE", /* Swedish Sweden */
738 "te_IN", /* Telugu India */
739 "tem_SL", /* Timne Sierra Leone */
740 "tet_ID", /* Tetum Indonesia */
741 "tg_TJ", /* Tajik Tajikistan */
742 "th_TH", /* Thai Thailand */
743 "tiv_NG", /* Tiv Nigeria */
744 "tk_TM", /* Turkmen Turkmenistan */
745 "tl_PH", /* Tagalog Philippines */
746 "to_TO", /* Tonga Tonga */
747 "tpi_PG", /* Tok Pisin Papua New Guinea */
748 "tr_TR", /* Turkish Turkey */
749 "tum_MW", /* Tumbuka Malawi */
750 "ug_CN", /* Uighur China */
751 "uk_UA", /* Ukrainian Ukraine */
752 "umb_AO", /* Umbundu Angola */
753 "ur_PK", /* Urdu Pakistan */
754 "uz_UZ", /* Uzbek Uzbekistan */
755 "ve_ZA", /* Venda South Africa */
756 "vi_VN", /* Vietnamese Vietnam */
757 "wa_BE", /* Walloon Belgium */
758 "wal_ET", /* Walamo Ethiopia */
759 "war_PH", /* Waray Philippines */
760 "wen_DE", /* Sorbian Germany */
761 "yao_MW", /* Yao Malawi */
762 "zap_MX" /* Zapotec Mexico */
763 };
764 const char *dot;
765 size_t i;
766
767 /* Remove the ".codeset" part from the locale. */
768 dot = strchr (locale, '.');
769 if (dot != NULL)
770 {
771 const char *codeset_end;
772 char *shorter_locale;
773
774 codeset_end = strpbrk (dot + 1, "_@");
775 if (codeset_end == NULL)
776 codeset_end = dot + strlen (dot);
777
778 shorter_locale = XNMALLOC (strlen (locale), char);
779 memcpy (shorter_locale, locale, dot - locale);
780 strcpy (shorter_locale + (dot - locale), codeset_end);
781 locale = shorter_locale;
782 }
783
784 /* If the territory is the language's principal territory, drop it. */
785 for (i = 0; i < SIZEOF (locales_with_principal_territory); i++)
786 if (strcmp (locale, locales_with_principal_territory[i]) == 0)
787 {
788 const char *language_end;
789 size_t len;
790 char *shorter_locale;
791
792 language_end = strchr (locale, '_');
793 if (language_end == NULL)
794 abort ();
795
796 len = language_end - locale;
797 shorter_locale = XNMALLOC (len + 1, char);
798 memcpy (shorter_locale, locale, len);
799 shorter_locale[len] = '\0';
800 locale = shorter_locale;
801 break;
802 }
803
804 return locale;
805 }
806
807
808 /* Return the language of a locale. */
809 static const char *
language_of_locale(const char * locale)810 language_of_locale (const char *locale)
811 {
812 const char *language_end;
813
814 language_end = strpbrk (locale, "_.@");
815 if (language_end != NULL)
816 {
817 size_t len;
818 char *result;
819
820 len = language_end - locale;
821 result = XNMALLOC (len + 1, char);
822 memcpy (result, locale, len);
823 result[len] = '\0';
824
825 return result;
826 }
827 else
828 return locale;
829 }
830
831
832 /* Return the most likely desired charset for the PO file, as a portable
833 charset name. */
834 static const char *
canonical_locale_charset()835 canonical_locale_charset ()
836 {
837 const char *tmp;
838 char *old_LC_ALL;
839 const char *charset;
840
841 /* Save LC_ALL environment variable. */
842
843 tmp = getenv ("LC_ALL");
844 old_LC_ALL = (tmp != NULL ? xstrdup (tmp) : NULL);
845
846 xsetenv ("LC_ALL", locale, 1);
847
848 if (setlocale (LC_ALL, "") == NULL)
849 /* Nonexistent locale. Use anything. */
850 charset = "";
851 else
852 /* Get the locale's charset. */
853 charset = locale_charset ();
854
855 /* Restore LC_ALL environment variable. */
856
857 if (old_LC_ALL != NULL)
858 xsetenv ("LC_ALL", old_LC_ALL, 1), free (old_LC_ALL);
859 else
860 unsetenv ("LC_ALL");
861
862 setlocale (LC_ALL, "");
863
864 /* Canonicalize it. */
865 charset = po_charset_canonicalize (charset);
866 if (charset == NULL)
867 charset = po_charset_ascii;
868
869 return charset;
870 }
871
872
873 /* Return the English name of the language. */
874 static const char *
englishname_of_language()875 englishname_of_language ()
876 {
877 size_t i;
878
879 for (i = 0; i < language_table_size; i++)
880 if (strcmp (language_table[i].code, language) == 0)
881 return language_table[i].english;
882
883 return xasprintf ("Language %s", language);
884 }
885
886
887 /* Construct the value for the PACKAGE name. */
888 static const char *
project_id(const char * header)889 project_id (const char *header)
890 {
891 const char *old_field;
892
893 /* Return the first part of the Project-Id-Version field if present, assuming
894 it was already filled in by xgettext. */
895 old_field = get_field (header, "Project-Id-Version");
896 if (old_field != NULL && strcmp (old_field, "PACKAGE VERSION") != 0)
897 {
898 /* Remove the last word from old_field. */
899 const char *last_space;
900
901 last_space = strrchr (old_field, ' ');
902 if (last_space != NULL)
903 {
904 while (last_space > old_field && last_space[-1] == ' ')
905 last_space--;
906 if (last_space > old_field)
907 {
908 size_t package_len = last_space - old_field;
909 char *package = XNMALLOC (package_len + 1, char);
910 memcpy (package, old_field, package_len);
911 package[package_len] = '\0';
912
913 return package;
914 }
915 }
916 /* It contains no version, just a package name. */
917 return old_field;
918 }
919
920 /* On native Windows, a Bourne shell is generally not available.
921 Avoid error messages such as
922 "msginit.exe: subprocess ... failed: No such file or directory" */
923 #if !(defined _WIN32 && ! defined __CYGWIN__)
924 {
925 const char *gettextlibdir;
926 char *prog;
927 char *argv[3];
928 pid_t child;
929 int fd[1];
930 FILE *fp;
931 char *line;
932 size_t linesize;
933 size_t linelen;
934 int exitstatus;
935
936 gettextlibdir = getenv ("GETTEXTLIBDIR_SRCDIR");
937 if (gettextlibdir == NULL || gettextlibdir[0] == '\0')
938 gettextlibdir = relocate (LIBDIR "/gettext");
939
940 prog = xconcatenated_filename (gettextlibdir, "project-id", NULL);
941
942 /* Call the project-id shell script. */
943 argv[0] = BOURNE_SHELL;
944 argv[1] = prog;
945 argv[2] = NULL;
946 child = create_pipe_in (prog, BOURNE_SHELL, argv, DEV_NULL, false, true,
947 false, fd);
948 if (child == -1)
949 goto failed;
950
951 /* Retrieve its result. */
952 fp = fdopen (fd[0], "r");
953 if (fp == NULL)
954 {
955 error (0, errno, _("fdopen() failed"));
956 goto failed;
957 }
958
959 line = NULL; linesize = 0;
960 linelen = getline (&line, &linesize, fp);
961 if (linelen == (size_t)(-1))
962 {
963 error (0, 0, _("%s subprocess I/O error"), prog);
964 fclose (fp);
965 goto failed;
966 }
967 if (linelen > 0 && line[linelen - 1] == '\n')
968 line[linelen - 1] = '\0';
969
970 fclose (fp);
971
972 /* Remove zombie process from process list, and retrieve exit status. */
973 exitstatus = wait_subprocess (child, prog, false, false, true, false, NULL);
974 if (exitstatus != 0)
975 {
976 error (0, 0, _("%s subprocess failed with exit code %d"),
977 prog, exitstatus);
978 goto failed;
979 }
980
981 return line;
982 }
983
984 failed:
985 #endif
986 return "PACKAGE";
987 }
988
989
990 /* Construct the value for the Project-Id-Version field. */
991 static const char *
project_id_version(const char * header)992 project_id_version (const char *header)
993 {
994 const char *old_field;
995
996 /* Return the old value if present, assuming it was already filled in by
997 xgettext. */
998 old_field = get_field (header, "Project-Id-Version");
999 if (old_field != NULL && strcmp (old_field, "PACKAGE VERSION") != 0)
1000 return old_field;
1001
1002 /* On native Windows, a Bourne shell is generally not available.
1003 Avoid error messages such as
1004 "msginit.exe: subprocess ... failed: No such file or directory" */
1005 #if !(defined _WIN32 && ! defined __CYGWIN__)
1006 {
1007 const char *gettextlibdir;
1008 char *prog;
1009 char *argv[4];
1010 pid_t child;
1011 int fd[1];
1012 FILE *fp;
1013 char *line;
1014 size_t linesize;
1015 size_t linelen;
1016 int exitstatus;
1017
1018 gettextlibdir = getenv ("GETTEXTLIBDIR_SRCDIR");
1019 if (gettextlibdir == NULL || gettextlibdir[0] == '\0')
1020 gettextlibdir = relocate (LIBDIR "/gettext");
1021
1022 prog = xconcatenated_filename (gettextlibdir, "project-id", NULL);
1023
1024 /* Call the project-id shell script. */
1025 argv[0] = BOURNE_SHELL;
1026 argv[1] = prog;
1027 argv[2] = "yes";
1028 argv[3] = NULL;
1029 child = create_pipe_in (prog, BOURNE_SHELL, argv, DEV_NULL, false, true,
1030 false, fd);
1031 if (child == -1)
1032 goto failed;
1033
1034 /* Retrieve its result. */
1035 fp = fdopen (fd[0], "r");
1036 if (fp == NULL)
1037 {
1038 error (0, errno, _("fdopen() failed"));
1039 goto failed;
1040 }
1041
1042 line = NULL; linesize = 0;
1043 linelen = getline (&line, &linesize, fp);
1044 if (linelen == (size_t)(-1))
1045 {
1046 error (0, 0, _("%s subprocess I/O error"), prog);
1047 fclose (fp);
1048 goto failed;
1049 }
1050 if (linelen > 0 && line[linelen - 1] == '\n')
1051 line[linelen - 1] = '\0';
1052
1053 fclose (fp);
1054
1055 /* Remove zombie process from process list, and retrieve exit status. */
1056 exitstatus = wait_subprocess (child, prog, false, false, true, false, NULL);
1057 if (exitstatus != 0)
1058 {
1059 error (0, 0, _("%s subprocess failed with exit code %d"),
1060 prog, exitstatus);
1061 goto failed;
1062 }
1063
1064 return line;
1065 }
1066
1067 failed:
1068 #endif
1069 return "PACKAGE VERSION";
1070 }
1071
1072
1073 /* Construct the value for the PO-Revision-Date field. */
1074 static const char *
po_revision_date(const char * header)1075 po_revision_date (const char *header)
1076 {
1077 if (no_translator)
1078 /* Because the PO file is automatically generated, we use the
1079 POT-Creation-Date, not the current time. */
1080 return get_field (header, "POT-Creation-Date");
1081 else
1082 {
1083 /* Assume the translator will modify the PO file now. */
1084 time_t now;
1085
1086 time (&now);
1087 return po_strftime (&now);
1088 }
1089 }
1090
1091
1092 #if HAVE_PWD_H /* Only Unix, not native Windows. */
1093
1094 /* Returns the struct passwd entry for the current user. */
1095 static struct passwd *
get_user_pwd()1096 get_user_pwd ()
1097 {
1098 const char *username;
1099 struct passwd *userpasswd;
1100
1101 /* 1. attempt: getpwnam(getenv("USER")) */
1102 username = getenv ("USER");
1103 if (username != NULL)
1104 {
1105 errno = 0;
1106 userpasswd = getpwnam (username);
1107 if (userpasswd != NULL)
1108 return userpasswd;
1109 if (errno != 0)
1110 error (EXIT_FAILURE, errno, "getpwnam(\"%s\")", username);
1111 }
1112
1113 /* 2. attempt: getpwnam(getlogin()) */
1114 username = getlogin ();
1115 if (username != NULL)
1116 {
1117 errno = 0;
1118 userpasswd = getpwnam (username);
1119 if (userpasswd != NULL)
1120 return userpasswd;
1121 if (errno != 0)
1122 error (EXIT_FAILURE, errno, "getpwnam(\"%s\")", username);
1123 }
1124
1125 /* 3. attempt: getpwuid(getuid()) */
1126 errno = 0;
1127 userpasswd = getpwuid (getuid ());
1128 if (userpasswd != NULL)
1129 return userpasswd;
1130 if (errno != 0)
1131 error (EXIT_FAILURE, errno, "getpwuid(%ju)", (uintmax_t) getuid ());
1132
1133 return NULL;
1134 }
1135
1136 #endif
1137
1138
1139 /* Return the user's full name. */
1140 static const char *
get_user_fullname()1141 get_user_fullname ()
1142 {
1143 #if HAVE_PWD_H
1144 struct passwd *pwd;
1145
1146 pwd = get_user_pwd ();
1147 if (pwd != NULL)
1148 {
1149 const char *fullname;
1150 const char *fullname_end;
1151 char *result;
1152
1153 /* Return the pw_gecos field, up to the first comma (if any). */
1154 fullname = pwd->pw_gecos;
1155 fullname_end = strchr (fullname, ',');
1156 if (fullname_end == NULL)
1157 fullname_end = fullname + strlen (fullname);
1158
1159 result = XNMALLOC (fullname_end - fullname + 1, char);
1160 memcpy (result, fullname, fullname_end - fullname);
1161 result[fullname_end - fullname] = '\0';
1162
1163 return result;
1164 }
1165 #endif
1166
1167 return NULL;
1168 }
1169
1170
1171 /* Return the user's email address. */
1172 static const char *
get_user_email()1173 get_user_email ()
1174 {
1175 /* On native Windows, a Bourne shell is generally not available.
1176 Avoid error messages such as
1177 "msginit.exe: subprocess ... failed: No such file or directory" */
1178 #if !(defined _WIN32 && ! defined __CYGWIN__)
1179 {
1180 const char *prog = relocate (LIBDIR "/gettext/user-email");
1181 char *argv[4];
1182 pid_t child;
1183 int fd[1];
1184 FILE *fp;
1185 char *line;
1186 size_t linesize;
1187 size_t linelen;
1188 int exitstatus;
1189
1190 /* Ask the user for his email address. */
1191 argv[0] = BOURNE_SHELL;
1192 argv[1] = (char *) prog;
1193 argv[2] = (char *) _("\
1194 The new message catalog should contain your email address, so that users can\n\
1195 give you feedback about the translations, and so that maintainers can contact\n\
1196 you in case of unexpected technical problems.\n");
1197 argv[3] = NULL;
1198 child = create_pipe_in (prog, BOURNE_SHELL, argv, DEV_NULL, false, true,
1199 false, fd);
1200 if (child == -1)
1201 goto failed;
1202
1203 /* Retrieve his answer. */
1204 fp = fdopen (fd[0], "r");
1205 if (fp == NULL)
1206 {
1207 error (0, errno, _("fdopen() failed"));
1208 goto failed;
1209 }
1210
1211 line = NULL; linesize = 0;
1212 linelen = getline (&line, &linesize, fp);
1213 if (linelen == (size_t)(-1))
1214 {
1215 error (0, 0, _("%s subprocess I/O error"), prog);
1216 fclose (fp);
1217 goto failed;
1218 }
1219 if (linelen > 0 && line[linelen - 1] == '\n')
1220 line[linelen - 1] = '\0';
1221
1222 fclose (fp);
1223
1224 /* Remove zombie process from process list, and retrieve exit status. */
1225 exitstatus = wait_subprocess (child, prog, false, false, true, false, NULL);
1226 if (exitstatus != 0)
1227 {
1228 error (0, 0, _("%s subprocess failed with exit code %d"),
1229 prog, exitstatus);
1230 goto failed;
1231 }
1232
1233 return line;
1234 }
1235
1236 failed:
1237 #endif
1238 return "EMAIL@ADDRESS";
1239 }
1240
1241
1242 /* Construct the value for the Last-Translator field. */
1243 static const char *
last_translator()1244 last_translator ()
1245 {
1246 if (no_translator)
1247 return "Automatically generated";
1248 else
1249 {
1250 const char *fullname = get_user_fullname ();
1251 const char *email = get_user_email ();
1252
1253 if (fullname != NULL)
1254 return xasprintf ("%s <%s>", fullname, email);
1255 else
1256 return xasprintf ("<%s>", email);
1257 }
1258 }
1259
1260
1261 /* Return the name of the language used by the language team, in English. */
1262 static const char *
language_team_englishname()1263 language_team_englishname ()
1264 {
1265 size_t i;
1266
1267 /* Search for a name depending on the catalogname. */
1268 for (i = 0; i < language_variant_table_size; i++)
1269 if (strcmp (language_variant_table[i].code, catalogname) == 0)
1270 return language_variant_table[i].english;
1271
1272 /* Search for a name depending on the language only. */
1273 return englishname_of_language ();
1274 }
1275
1276
1277 /* Return the language team's mailing list address or homepage URL. */
1278 static const char *
language_team_address()1279 language_team_address ()
1280 {
1281 /* On native Windows, a Bourne shell is generally not available.
1282 Avoid error messages such as
1283 "msginit.exe: subprocess ... failed: No such file or directory" */
1284 #if !(defined _WIN32 && ! defined __CYGWIN__)
1285 {
1286 const char *prog = relocate (PROJECTSDIR "/team-address");
1287 char *argv[7];
1288 pid_t child;
1289 int fd[1];
1290 FILE *fp;
1291 char *line;
1292 size_t linesize;
1293 size_t linelen;
1294 int exitstatus;
1295
1296 /* Call the team-address shell script. */
1297 argv[0] = BOURNE_SHELL;
1298 argv[1] = (char *) prog;
1299 argv[2] = (char *) relocate (PROJECTSDIR);
1300 argv[3] = (char *) relocate (LIBDIR "/gettext");
1301 argv[4] = (char *) catalogname;
1302 argv[5] = (char *) language;
1303 argv[6] = NULL;
1304 child = create_pipe_in (prog, BOURNE_SHELL, argv, DEV_NULL, false, true,
1305 false, fd);
1306 if (child == -1)
1307 goto failed;
1308
1309 /* Retrieve its result. */
1310 fp = fdopen (fd[0], "r");
1311 if (fp == NULL)
1312 {
1313 error (0, errno, _("fdopen() failed"));
1314 goto failed;
1315 }
1316
1317 line = NULL; linesize = 0;
1318 linelen = getline (&line, &linesize, fp);
1319 if (linelen == (size_t)(-1))
1320 line = "";
1321 else if (linelen > 0 && line[linelen - 1] == '\n')
1322 line[linelen - 1] = '\0';
1323
1324 fclose (fp);
1325
1326 /* Remove zombie process from process list, and retrieve exit status. */
1327 exitstatus = wait_subprocess (child, prog, false, false, true, false, NULL);
1328 if (exitstatus != 0)
1329 {
1330 error (0, 0, _("%s subprocess failed with exit code %d"),
1331 prog, exitstatus);
1332 goto failed;
1333 }
1334
1335 return line;
1336 }
1337
1338 failed:
1339 #endif
1340 return "";
1341 }
1342
1343
1344 /* Construct the value for the Language-Team field. */
1345 static const char *
language_team()1346 language_team ()
1347 {
1348 if (no_translator)
1349 return "none";
1350 else
1351 {
1352 const char *englishname = language_team_englishname ();
1353 const char *address = language_team_address ();
1354
1355 if (address != NULL && address[0] != '\0')
1356 return xasprintf ("%s %s", englishname, address);
1357 else
1358 return englishname;
1359 }
1360 }
1361
1362
1363 /* Construct the value for the Language field. */
1364 static const char *
language_value()1365 language_value ()
1366 {
1367 return catalogname;
1368 }
1369
1370
1371 /* Construct the value for the MIME-Version field. */
1372 static const char *
mime_version()1373 mime_version ()
1374 {
1375 return "1.0";
1376 }
1377
1378
1379 /* Construct the value for the Content-Type field. */
1380 static const char *
content_type(const char * header)1381 content_type (const char *header)
1382 {
1383 bool was_utf8;
1384 const char *old_field;
1385
1386 /* If the POT file contains charset=UTF-8, it means that the POT file
1387 contains non-ASCII characters, and we keep the UTF-8 encoding.
1388 Otherwise, when the POT file is plain ASCII, we use the locale's
1389 encoding. */
1390 was_utf8 = false;
1391 old_field = get_field (header, "Content-Type");
1392 if (old_field != NULL)
1393 {
1394 const char *charsetstr = c_strstr (old_field, "charset=");
1395
1396 if (charsetstr != NULL)
1397 {
1398 charsetstr += strlen ("charset=");
1399 was_utf8 = (c_strcasecmp (charsetstr, "UTF-8") == 0);
1400 }
1401 }
1402 return xasprintf ("text/plain; charset=%s",
1403 was_utf8 ? "UTF-8" : canonical_locale_charset ());
1404 }
1405
1406
1407 /* Construct the value for the Content-Transfer-Encoding field. */
1408 static const char *
content_transfer_encoding()1409 content_transfer_encoding ()
1410 {
1411 return "8bit";
1412 }
1413
1414
1415 /* Construct the value for the Plural-Forms field. */
1416 static const char *
plural_forms()1417 plural_forms ()
1418 {
1419 const char *gettextcldrdir;
1420 char *prog = NULL;
1421 size_t i;
1422
1423 /* Search for a formula depending on the catalogname. */
1424 for (i = 0; i < plural_table_size; i++)
1425 if (strcmp (plural_table[i].lang, catalogname) == 0)
1426 return plural_table[i].value;
1427
1428 /* Search for a formula depending on the language only. */
1429 for (i = 0; i < plural_table_size; i++)
1430 if (strcmp (plural_table[i].lang, language) == 0)
1431 return plural_table[i].value;
1432
1433 gettextcldrdir = getenv ("GETTEXTCLDRDIR");
1434 if (gettextcldrdir != NULL && gettextcldrdir[0] != '\0')
1435 {
1436 const char *gettextlibdir;
1437 char *dirs[3], *last_dir;
1438 char *argv[4];
1439 pid_t child;
1440 int fd[1];
1441 FILE *fp;
1442 char *line;
1443 size_t linesize;
1444 size_t linelen;
1445 int exitstatus;
1446
1447 gettextlibdir = getenv ("GETTEXTLIBDIR_BUILDDIR");
1448 if (gettextlibdir == NULL || gettextlibdir[0] == '\0')
1449 gettextlibdir = relocate (LIBDIR "/gettext");
1450
1451 prog = xconcatenated_filename (gettextlibdir, "cldr-plurals", EXEEXT);
1452
1453 last_dir = xstrdup (gettextcldrdir);
1454 dirs[0] = "common";
1455 dirs[1] = "supplemental";
1456 dirs[2] = "plurals.xml";
1457 for (i = 0; i < SIZEOF (dirs); i++)
1458 {
1459 char *dir = xconcatenated_filename (last_dir, dirs[i], NULL);
1460 free (last_dir);
1461 last_dir = dir;
1462 }
1463
1464 /* Call the cldr-plurals command.
1465 argv[0] must be prog, not just the base name "cldr-plurals",
1466 because on Cygwin in a build with --enable-shared, the libtool
1467 wrapper of cldr-plurals.exe apparently needs this. */
1468 argv[0] = prog;
1469 argv[1] = (char *) language;
1470 argv[2] = last_dir;
1471 argv[3] = NULL;
1472 child = create_pipe_in (prog, prog, argv, DEV_NULL,
1473 false, true, false,
1474 fd);
1475 free (last_dir);
1476 if (child == -1)
1477 goto failed;
1478
1479 /* Retrieve its result. */
1480 fp = fdopen (fd[0], "r");
1481 if (fp == NULL)
1482 {
1483 error (0, errno, _("fdopen() failed"));
1484 goto failed;
1485 }
1486
1487 line = NULL; linesize = 0;
1488 linelen = getline (&line, &linesize, fp);
1489 if (linelen == (size_t)(-1))
1490 {
1491 error (0, 0, _("%s subprocess I/O error"), prog);
1492 fclose (fp);
1493 goto failed;
1494 }
1495 if (linelen > 0 && line[linelen - 1] == '\n')
1496 {
1497 line[linelen - 1] = '\0';
1498 #if defined _WIN32 && ! defined __CYGWIN__
1499 if (linelen > 1 && line[linelen - 2] == '\r')
1500 line[linelen - 2] = '\0';
1501 #endif
1502 }
1503
1504 fclose (fp);
1505
1506 /* Remove zombie process from process list, and retrieve exit status. */
1507 exitstatus = wait_subprocess (child, prog, false, false, true, false,
1508 NULL);
1509 if (exitstatus != 0)
1510 {
1511 error (0, 0, _("%s subprocess failed with exit code %d"),
1512 prog, exitstatus);
1513 goto failed;
1514 }
1515
1516 return line;
1517 }
1518
1519 failed:
1520 free (prog);
1521 return NULL;
1522 }
1523
1524
1525 static struct
1526 {
1527 const char *name;
1528 const char * (*getter0) (void);
1529 const char * (*getter1) (const char *header);
1530 }
1531 fields[] =
1532 {
1533 { "Project-Id-Version", NULL, project_id_version },
1534 { "PO-Revision-Date", NULL, po_revision_date },
1535 { "Last-Translator", last_translator, NULL },
1536 { "Language-Team", language_team, NULL },
1537 { "Language", language_value, NULL },
1538 { "MIME-Version", mime_version, NULL },
1539 { "Content-Type", NULL, content_type },
1540 { "Content-Transfer-Encoding", content_transfer_encoding, NULL },
1541 { "Plural-Forms", plural_forms, NULL }
1542 };
1543
1544 #define NFIELDS SIZEOF (fields)
1545 #define FIELD_LAST_TRANSLATOR 2
1546
1547
1548 /* Retrieve a freshly allocated copy of a field's value. */
1549 static char *
get_field(const char * header,const char * field)1550 get_field (const char *header, const char *field)
1551 {
1552 size_t len = strlen (field);
1553 const char *line;
1554
1555 for (line = header;;)
1556 {
1557 if (strncmp (line, field, len) == 0 && line[len] == ':')
1558 {
1559 const char *value_start;
1560 const char *value_end;
1561 char *value;
1562
1563 value_start = line + len + 1;
1564 if (*value_start == ' ')
1565 value_start++;
1566 value_end = strchr (value_start, '\n');
1567 if (value_end == NULL)
1568 value_end = value_start + strlen (value_start);
1569
1570 value = XNMALLOC (value_end - value_start + 1, char);
1571 memcpy (value, value_start, value_end - value_start);
1572 value[value_end - value_start] = '\0';
1573
1574 return value;
1575 }
1576
1577 line = strchr (line, '\n');
1578 if (line != NULL)
1579 line++;
1580 else
1581 break;
1582 }
1583
1584 return NULL;
1585 }
1586
1587 /* Add a field with value to a header, and return the new header. */
1588 static char *
put_field(const char * old_header,const char * field,const char * value)1589 put_field (const char *old_header, const char *field, const char *value)
1590 {
1591 size_t len = strlen (field);
1592 const char *line;
1593 char *new_header;
1594 char *p;
1595
1596 for (line = old_header;;)
1597 {
1598 if (strncmp (line, field, len) == 0 && line[len] == ':')
1599 {
1600 const char *value_start;
1601 const char *value_end;
1602
1603 value_start = line + len + 1;
1604 if (*value_start == ' ')
1605 value_start++;
1606 value_end = strchr (value_start, '\n');
1607 if (value_end == NULL)
1608 value_end = value_start + strlen (value_start);
1609
1610 new_header = XNMALLOC (strlen (old_header)
1611 - (value_end - value_start)
1612 + strlen (value)
1613 + (*value_end != '\n' ? 1 : 0)
1614 + 1,
1615 char);
1616 p = new_header;
1617 memcpy (p, old_header, value_start - old_header);
1618 p += value_start - old_header;
1619 memcpy (p, value, strlen (value));
1620 p += strlen (value);
1621 if (*value_end != '\n')
1622 *p++ = '\n';
1623 strcpy (p, value_end);
1624
1625 return new_header;
1626 }
1627
1628 line = strchr (line, '\n');
1629 if (line != NULL)
1630 line++;
1631 else
1632 break;
1633 }
1634
1635 new_header = XNMALLOC (strlen (old_header) + 1
1636 + len + 2 + strlen (value) + 1
1637 + 1,
1638 char);
1639 p = new_header;
1640 memcpy (p, old_header, strlen (old_header));
1641 p += strlen (old_header);
1642 if (p > new_header && p[-1] != '\n')
1643 *p++ = '\n';
1644 memcpy (p, field, len);
1645 p += len;
1646 *p++ = ':';
1647 *p++ = ' ';
1648 memcpy (p, value, strlen (value));
1649 p += strlen (value);
1650 *p++ = '\n';
1651 *p = '\0';
1652
1653 return new_header;
1654 }
1655
1656
1657 /* Return the title format string. */
1658 static const char *
get_title()1659 get_title ()
1660 {
1661 /* This is tricky. We want the translation in the given locale specified by
1662 the command line, not the current locale. But we want it in the encoding
1663 that we put into the header entry, not the encoding of that locale.
1664 We could avoid the use of OUTPUT_CHARSET by using a separate message
1665 catalog and bind_textdomain_codeset(), but that doesn't seem worth the
1666 trouble for one single message. */
1667 const char *encoding;
1668 const char *tmp;
1669 char *old_LC_ALL;
1670 char *old_LANGUAGE;
1671 char *old_OUTPUT_CHARSET;
1672 const char *msgid;
1673 const char *english;
1674 const char *result;
1675
1676 encoding = canonical_locale_charset ();
1677
1678 /* First, the English title. */
1679 english = xasprintf ("%s translations for %%s package",
1680 englishname_of_language ());
1681
1682 /* Save LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables. */
1683
1684 tmp = getenv ("LC_ALL");
1685 old_LC_ALL = (tmp != NULL ? xstrdup (tmp) : NULL);
1686
1687 tmp = getenv ("LANGUAGE");
1688 old_LANGUAGE = (tmp != NULL ? xstrdup (tmp) : NULL);
1689
1690 tmp = getenv ("OUTPUT_CHARSET");
1691 old_OUTPUT_CHARSET = (tmp != NULL ? xstrdup (tmp) : NULL);
1692
1693 xsetenv ("LC_ALL", locale, 1);
1694 unsetenv ("LANGUAGE");
1695 xsetenv ("OUTPUT_CHARSET", encoding, 1);
1696
1697 if (setlocale (LC_ALL, "") == NULL)
1698 /* Nonexistent locale. Use the English title. */
1699 result = english;
1700 else
1701 {
1702 /* Fetch the translation. */
1703 /* TRANSLATORS: "English" needs to be replaced by your language.
1704 For example in it.po write "Traduzioni italiani ...",
1705 *not* "Traduzioni inglesi ...". */
1706 msgid = N_("English translations for %s package");
1707 result = gettext (msgid);
1708 if (result != msgid && strcmp (result, msgid) != 0)
1709 /* Use the English and the foreign title. */
1710 result = xasprintf ("%s\n%s", english, result);
1711 else
1712 /* No translation found. Use the English title. */
1713 result = english;
1714 }
1715
1716 /* Restore LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables. */
1717
1718 if (old_LC_ALL != NULL)
1719 xsetenv ("LC_ALL", old_LC_ALL, 1), free (old_LC_ALL);
1720 else
1721 unsetenv ("LC_ALL");
1722
1723 if (old_LANGUAGE != NULL)
1724 xsetenv ("LANGUAGE", old_LANGUAGE, 1), free (old_LANGUAGE);
1725 else
1726 unsetenv ("LANGUAGE");
1727
1728 if (old_OUTPUT_CHARSET != NULL)
1729 xsetenv ("OUTPUT_CHARSET", old_OUTPUT_CHARSET, 1), free (old_OUTPUT_CHARSET);
1730 else
1731 unsetenv ("OUTPUT_CHARSET");
1732
1733 setlocale (LC_ALL, "");
1734
1735 return result;
1736 }
1737
1738
1739 /* Perform a set of substitutions in a string and return the resulting
1740 string. When subst[j][0] found, it is replaced with subst[j][1].
1741 subst[j][0] must not be the empty string. */
1742 static const char *
subst_string(const char * str,unsigned int nsubst,const char * (* subst)[2])1743 subst_string (const char *str,
1744 unsigned int nsubst, const char *(*subst)[2])
1745 {
1746 if (nsubst > 0)
1747 {
1748 char *malloced = NULL;
1749 size_t *substlen;
1750 size_t i;
1751 unsigned int j;
1752
1753 substlen = (size_t *) xmalloca (nsubst * sizeof (size_t));
1754 for (j = 0; j < nsubst; j++)
1755 {
1756 substlen[j] = strlen (subst[j][0]);
1757 if (substlen[j] == 0)
1758 abort ();
1759 }
1760
1761 for (i = 0;;)
1762 {
1763 if (str[i] == '\0')
1764 break;
1765 for (j = 0; j < nsubst; j++)
1766 if (*(str + i) == *subst[j][0]
1767 && strncmp (str + i, subst[j][0], substlen[j]) == 0)
1768 {
1769 size_t replacement_len = strlen (subst[j][1]);
1770 size_t new_len = strlen (str) - substlen[j] + replacement_len;
1771 char *new_str = XNMALLOC (new_len + 1, char);
1772 memcpy (new_str, str, i);
1773 memcpy (new_str + i, subst[j][1], replacement_len);
1774 strcpy (new_str + i + replacement_len, str + i + substlen[j]);
1775 if (malloced != NULL)
1776 free (malloced);
1777 str = new_str;
1778 malloced = new_str;
1779 i += replacement_len;
1780 break;
1781 }
1782 if (j == nsubst)
1783 i++;
1784 }
1785
1786 freea (substlen);
1787 }
1788
1789 return str;
1790 }
1791
1792 /* Perform a set of substitutions on each string of a string list.
1793 When subst[j][0] found, it is replaced with subst[j][1]. subst[j][0]
1794 must not be the empty string. */
1795 static void
subst_string_list(string_list_ty * slp,unsigned int nsubst,const char * (* subst)[2])1796 subst_string_list (string_list_ty *slp,
1797 unsigned int nsubst, const char *(*subst)[2])
1798 {
1799 size_t j;
1800
1801 for (j = 0; j < slp->nitems; j++)
1802 slp->item[j] = subst_string (slp->item[j], nsubst, subst);
1803 }
1804
1805
1806 /* Fill the templates in all fields of the header entry. */
1807 static msgdomain_list_ty *
fill_header(msgdomain_list_ty * mdlp)1808 fill_header (msgdomain_list_ty *mdlp)
1809 {
1810 /* Cache the strings filled in, for use when there are multiple domains
1811 and a header entry for each domain. */
1812 const char *field_value[NFIELDS];
1813 size_t k, j, i;
1814
1815 for (i = 0; i < NFIELDS; i++)
1816 field_value[i] = NULL;
1817
1818 for (k = 0; k < mdlp->nitems; k++)
1819 {
1820 message_list_ty *mlp = mdlp->item[k]->messages;
1821
1822 if (mlp->nitems > 0)
1823 {
1824 message_ty *header_mp = NULL;
1825 char *header;
1826
1827 /* Search the header entry. */
1828 for (j = 0; j < mlp->nitems; j++)
1829 if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
1830 {
1831 header_mp = mlp->item[j];
1832 break;
1833 }
1834
1835 /* If it wasn't found, provide one. */
1836 if (header_mp == NULL)
1837 {
1838 static lex_pos_ty pos = { __FILE__, __LINE__ };
1839
1840 header_mp = message_alloc (NULL, "", NULL, "", 1, &pos);
1841 message_list_prepend (mlp, header_mp);
1842 }
1843
1844 header = xstrdup (header_mp->msgstr);
1845
1846 /* Fill in the fields. */
1847 for (i = 0; i < NFIELDS; i++)
1848 {
1849 if (field_value[i] == NULL)
1850 field_value[i] =
1851 (fields[i].getter1 != NULL
1852 ? fields[i].getter1 (header)
1853 : fields[i].getter0 ());
1854
1855 if (field_value[i] != NULL)
1856 {
1857 char *old_header = header;
1858 header = put_field (header, fields[i].name, field_value[i]);
1859 free (old_header);
1860 }
1861 }
1862
1863 /* Replace the old translation in the header entry. */
1864 header_mp->msgstr = header;
1865 header_mp->msgstr_len = strlen (header) + 1;
1866
1867 /* Update the comments in the header entry. */
1868 if (header_mp->comment != NULL)
1869 {
1870 const char *subst[4][2];
1871 const char *id;
1872 time_t now;
1873
1874 id = project_id (header);
1875 subst[0][0] = "SOME DESCRIPTIVE TITLE";
1876 subst[0][1] = xasprintf (get_title (), id, id);
1877 subst[1][0] = "PACKAGE";
1878 subst[1][1] = id;
1879 subst[2][0] = "FIRST AUTHOR <EMAIL@ADDRESS>";
1880 subst[2][1] = field_value[FIELD_LAST_TRANSLATOR];
1881 subst[3][0] = "YEAR";
1882 subst[3][1] =
1883 xasprintf ("%d",
1884 (time (&now), (localtime (&now))->tm_year + 1900));
1885 subst_string_list (header_mp->comment, SIZEOF (subst), subst);
1886 }
1887
1888 /* Finally remove the fuzzy attribute. */
1889 header_mp->is_fuzzy = false;
1890 }
1891 }
1892
1893 return mdlp;
1894 }
1895
1896
1897 /* Update the msgstr plural entries according to the nplurals count. */
1898 static msgdomain_list_ty *
update_msgstr_plurals(msgdomain_list_ty * mdlp)1899 update_msgstr_plurals (msgdomain_list_ty *mdlp)
1900 {
1901 size_t k;
1902
1903 for (k = 0; k < mdlp->nitems; k++)
1904 {
1905 message_list_ty *mlp = mdlp->item[k]->messages;
1906 message_ty *header_entry;
1907 unsigned long int nplurals;
1908 char *untranslated_plural_msgstr;
1909 size_t j;
1910
1911 header_entry = message_list_search (mlp, NULL, "");
1912 nplurals = get_plural_count (header_entry ? header_entry->msgstr : NULL);
1913 untranslated_plural_msgstr = XNMALLOC (nplurals, char);
1914 memset (untranslated_plural_msgstr, '\0', nplurals);
1915
1916 for (j = 0; j < mlp->nitems; j++)
1917 {
1918 message_ty *mp = mlp->item[j];
1919 bool is_untranslated;
1920 const char *p;
1921 const char *pend;
1922
1923 if (mp->msgid_plural != NULL)
1924 {
1925 /* Test if mp is untranslated. (It most likely is.) */
1926 is_untranslated = true;
1927 for (p = mp->msgstr, pend = p + mp->msgstr_len; p < pend; p++)
1928 if (*p != '\0')
1929 {
1930 is_untranslated = false;
1931 break;
1932 }
1933 if (is_untranslated)
1934 {
1935 /* Change mp->msgstr_len consecutive empty strings into
1936 nplurals consecutive empty strings. */
1937 if (nplurals > mp->msgstr_len)
1938 mp->msgstr = untranslated_plural_msgstr;
1939 mp->msgstr_len = nplurals;
1940 }
1941 }
1942 }
1943 }
1944 return mdlp;
1945 }
1946