1 /* Writing tcl/msgcat .msg files.
2 Copyright (C) 2002-2003, 2005, 2007-2009, 2016 Free Software
3 Foundation, Inc.
4 Written by Bruno Haible <bruno@clisp.org>, 2002.
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <https://www.gnu.org/licenses/>. */
18
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22 #include <alloca.h>
23
24 /* Specification. */
25 #include "write-tcl.h"
26
27 #include <errno.h>
28 #include <stdbool.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include "error.h"
34 #include "xerror.h"
35 #include "message.h"
36 #include "msgl-iconv.h"
37 #include "msgl-header.h"
38 #include "po-charset.h"
39 #include "xalloc.h"
40 #include "xmalloca.h"
41 #include "concat-filename.h"
42 #include "fwriteerror.h"
43 #include "unistr.h"
44 #include "gettext.h"
45
46 #define _(str) gettext (str)
47
48
49 /* Write a string in Tcl Unicode notation to the given stream.
50 Tcl 8 uses Unicode for its internal string representation.
51 In tcl-8.3.3, the .msg files are read in using the locale dependent
52 encoding. The only way to specify strings in an encoding independent
53 form is the \unnnn notation. Newer tcl versions have this fixed:
54 they read the .msg files in UTF-8 encoding. */
55 static void
write_tcl_string(FILE * stream,const char * str)56 write_tcl_string (FILE *stream, const char *str)
57 {
58 static const char hexdigit[] = "0123456789abcdef";
59 const char *str_limit = str + strlen (str);
60
61 fprintf (stream, "\"");
62 while (str < str_limit)
63 {
64 ucs4_t uc;
65 unsigned int count;
66 count = u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
67 if (uc < 0x10000)
68 {
69 /* Single UCS-2 'char'. */
70 if (uc == 0x000a)
71 fprintf (stream, "\\n");
72 else if (uc == 0x000d)
73 fprintf (stream, "\\r");
74 else if (uc == 0x0022)
75 fprintf (stream, "\\\"");
76 else if (uc == 0x0024)
77 fprintf (stream, "\\$");
78 else if (uc == 0x005b)
79 fprintf (stream, "\\[");
80 else if (uc == 0x005c)
81 fprintf (stream, "\\\\");
82 else if (uc == 0x005d)
83 fprintf (stream, "\\]");
84 /* No need to escape '{' and '}' because we don't have opening
85 braces outside the strings. */
86 #if 0
87 else if (uc == 0x007b)
88 fprintf (stream, "\\{");
89 else if (uc == 0x007d)
90 fprintf (stream, "\\}");
91 #endif
92 else if (uc >= 0x0020 && uc < 0x007f)
93 fprintf (stream, "%c", (int) uc);
94 else
95 fprintf (stream, "\\u%c%c%c%c",
96 hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f],
97 hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]);
98 }
99 else
100 /* The \unnnn notation doesn't support characters >= 0x10000.
101 We output them as UTF-8 byte sequences and hope that either
102 the Tcl version reading them will be new enough or that the
103 user is using an UTF-8 locale. */
104 fwrite (str, 1, count, stream);
105 str += count;
106 }
107 fprintf (stream, "\"");
108 }
109
110
111 static void
write_msg(FILE * output_file,message_list_ty * mlp,const char * locale_name)112 write_msg (FILE *output_file, message_list_ty *mlp, const char *locale_name)
113 {
114 size_t j;
115
116 /* We don't care about esthetic formattic of the output (like respecting
117 a maximum line width, or including the translator comments) because
118 the \unnnn notation is unesthetic anyway. Translators shall edit
119 the PO file. */
120 for (j = 0; j < mlp->nitems; j++)
121 {
122 message_ty *mp = mlp->item[j];
123
124 if (is_header (mp))
125 /* Tcl's msgcat unit ignores this, but msgunfmt needs it. */
126 fprintf (output_file, "set ::msgcat::header ");
127 else
128 {
129 fprintf (output_file, "::msgcat::mcset %s ", locale_name);
130 write_tcl_string (output_file, mp->msgid);
131 fprintf (output_file, " ");
132 }
133 write_tcl_string (output_file, mp->msgstr);
134 fprintf (output_file, "\n");
135 }
136 }
137
138 int
msgdomain_write_tcl(message_list_ty * mlp,const char * canon_encoding,const char * locale_name,const char * directory)139 msgdomain_write_tcl (message_list_ty *mlp, const char *canon_encoding,
140 const char *locale_name,
141 const char *directory)
142 {
143 /* If no entry for this domain don't even create the file. */
144 if (mlp->nitems == 0)
145 return 0;
146
147 /* Determine whether mlp has entries with context. */
148 {
149 bool has_context;
150 size_t j;
151
152 has_context = false;
153 for (j = 0; j < mlp->nitems; j++)
154 if (mlp->item[j]->msgctxt != NULL)
155 has_context = true;
156 if (has_context)
157 {
158 multiline_error (xstrdup (""),
159 xstrdup (_("\
160 message catalog has context dependent translations\n\
161 but the Tcl message catalog format doesn't support contexts\n")));
162 return 1;
163 }
164 }
165
166 /* Determine whether mlp has plural entries. */
167 {
168 bool has_plural;
169 size_t j;
170
171 has_plural = false;
172 for (j = 0; j < mlp->nitems; j++)
173 if (mlp->item[j]->msgid_plural != NULL)
174 has_plural = true;
175 if (has_plural)
176 {
177 multiline_error (xstrdup (""),
178 xstrdup (_("\
179 message catalog has plural form translations\n\
180 but the Tcl message catalog format doesn't support plural handling\n")));
181 return 1;
182 }
183 }
184
185 /* Convert the messages to Unicode. */
186 iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
187
188 /* Support for "reproducible builds": Delete information that may vary
189 between builds in the same conditions. */
190 message_list_delete_header_field (mlp, "POT-Creation-Date:");
191
192 /* Now create the file. */
193 {
194 size_t len;
195 char *frobbed_locale_name;
196 char *p;
197 char *file_name;
198 FILE *output_file;
199
200 /* Convert the locale name to lowercase and remove any encoding. */
201 len = strlen (locale_name);
202 frobbed_locale_name = (char *) xmalloca (len + 1);
203 memcpy (frobbed_locale_name, locale_name, len + 1);
204 for (p = frobbed_locale_name; *p != '\0'; p++)
205 if (*p >= 'A' && *p <= 'Z')
206 *p = *p - 'A' + 'a';
207 else if (*p == '.')
208 {
209 *p = '\0';
210 break;
211 }
212
213 file_name = xconcatenated_filename (directory, frobbed_locale_name, ".msg");
214
215 output_file = fopen (file_name, "w");
216 if (output_file == NULL)
217 {
218 error (0, errno, _("error while opening \"%s\" for writing"),
219 file_name);
220 freea (frobbed_locale_name);
221 return 1;
222 }
223
224 write_msg (output_file, mlp, frobbed_locale_name);
225
226 /* Make sure nothing went wrong. */
227 if (fwriteerror (output_file))
228 error (EXIT_FAILURE, errno, _("error while writing \"%s\" file"),
229 file_name);
230
231 freea (frobbed_locale_name);
232 }
233
234 return 0;
235 }
236