1 /* Writing NeXTstep/GNUstep .strings files.
2 Copyright (C) 2003, 2006 Free Software Foundation, Inc.
3 Written by Bruno Haible <bruno@clisp.org>, 2003.
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 2, or (at your option)
8 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, write to the Free Software Foundation,
17 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
18
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
23 /* Specification. */
24 #include "write-stringtable.h"
25
26 #include <stdbool.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include "message.h"
32 #include "msgl-ascii.h"
33 #include "msgl-iconv.h"
34 #include "po-charset.h"
35 #include "c-strstr.h"
36 #include "write-po.h"
37
38 /* The format of NeXTstep/GNUstep .strings files is documented in
39 gnustep-base-1.8.0/Tools/make_strings/Using.txt
40 and in the comments of method propertyListFromStringsFileFormat in
41 gnustep-base-1.8.0/Source/NSString.m
42 In summary, it's a Objective-C like file with pseudo-assignments of the form
43 "key" = "value";
44 where the key is the msgid and the value is the msgstr.
45 */
46
47 /* Handling of comments: We copy all comments from the PO file to the
48 .strings file. This is not really needed; it's a service for translators
49 who don't like PO files and prefer to maintain the .strings file. */
50
51 /* Since the interpretation of text files in GNUstep depends on the locale's
52 encoding if they don't have a BOM, we choose one of three encodings with
53 a BOM: UCS-2BE, UCS-2LE, UTF-8. Since the first two of these don't cope
54 with all of Unicode and we don't know whether GNUstep will switch to
55 UTF-16 instead of UCS-2, we use UTF-8 with BOM. BOMs are bad because they
56 get in the way when concatenating files, but here we have no choice. */
57
58 /* Writes a key or value to the file, without newline. */
59 static void
write_escaped_string(FILE * fp,const char * str)60 write_escaped_string (FILE *fp, const char *str)
61 {
62 const char *str_limit = str + strlen (str);
63
64 putc ('"', fp);
65 while (str < str_limit)
66 {
67 unsigned char c = (unsigned char) *str++;
68
69 if (c == '\t')
70 {
71 putc ('\\', fp);
72 putc ('t', fp);
73 }
74 else if (c == '\n')
75 {
76 putc ('\\', fp);
77 putc ('n', fp);
78 }
79 else if (c == '\r')
80 {
81 putc ('\\', fp);
82 putc ('r', fp);
83 }
84 else if (c == '\f')
85 {
86 putc ('\\', fp);
87 putc ('f', fp);
88 }
89 else if (c == '\\' || c == '"')
90 {
91 putc ('\\', fp);
92 putc (c, fp);
93 }
94 else
95 putc (c, fp);
96 }
97 putc ('"', fp);
98 }
99
100 /* Writes a message to the file. */
101 static void
write_message(FILE * fp,const message_ty * mp,size_t page_width,bool debug)102 write_message (FILE *fp, const message_ty *mp, size_t page_width, bool debug)
103 {
104 /* Print translator comment if available. */
105 if (mp->comment != NULL)
106 {
107 size_t j;
108
109 for (j = 0; j < mp->comment->nitems; ++j)
110 {
111 const char *s = mp->comment->item[j];
112
113 /* Test whether it is safe to output the comment in C style, or
114 whether we need C++ style for it. */
115 if (c_strstr (s, "*/") == NULL)
116 {
117 fputs ("/*", fp);
118 if (*s != '\0' && *s != '\n' && *s != ' ')
119 putc (' ', fp);
120 fputs (s, fp);
121 fputs (" */\n", fp);
122 }
123 else
124 do
125 {
126 const char *e;
127 fputs ("//", fp);
128 if (*s != '\0' && *s != '\n' && *s != ' ')
129 putc (' ', fp);
130 e = strchr (s, '\n');
131 if (e == NULL)
132 {
133 fputs (s, fp);
134 s = NULL;
135 }
136 else
137 {
138 fwrite (s, 1, e - s, fp);
139 s = e + 1;
140 }
141 putc ('\n', fp);
142 }
143 while (s != NULL);
144 }
145 }
146
147 /* Print xgettext extracted comments. */
148 if (mp->comment_dot != NULL)
149 {
150 size_t j;
151
152 for (j = 0; j < mp->comment_dot->nitems; ++j)
153 {
154 const char *s = mp->comment_dot->item[j];
155
156 /* Test whether it is safe to output the comment in C style, or
157 whether we need C++ style for it. */
158 if (c_strstr (s, "*/") == NULL)
159 {
160 fputs ("/* Comment: ", fp);
161 fputs (s, fp);
162 fputs (" */\n", fp);
163 }
164 else
165 {
166 bool first = true;
167 do
168 {
169 const char *e;
170 fputs ("//", fp);
171 if (first || (*s != '\0' && *s != '\n' && *s != ' '))
172 putc (' ', fp);
173 if (first)
174 fputs ("Comment: ", fp);
175 e = strchr (s, '\n');
176 if (e == NULL)
177 {
178 fputs (s, fp);
179 s = NULL;
180 }
181 else
182 {
183 fwrite (s, 1, e - s, fp);
184 s = e + 1;
185 }
186 putc ('\n', fp);
187 first = false;
188 }
189 while (s != NULL);
190 }
191 }
192 }
193
194 /* Print the file position comments. */
195 if (mp->filepos_count != 0)
196 {
197 size_t j;
198
199 for (j = 0; j < mp->filepos_count; ++j)
200 {
201 lex_pos_ty *pp = &mp->filepos[j];
202 char *cp = pp->file_name;
203 while (cp[0] == '.' && cp[1] == '/')
204 cp += 2;
205 fprintf (fp, "/* File: %s:%ld */\n", cp, (long) pp->line_number);
206 }
207 }
208
209 /* Print flag information in special comment. */
210 if (mp->is_fuzzy || mp->msgstr[0] == '\0')
211 fputs ("/* Flag: untranslated */\n", fp);
212 if (mp->obsolete)
213 fputs ("/* Flag: unmatched */\n", fp);
214 {
215 size_t i;
216 for (i = 0; i < NFORMATS; i++)
217 if (significant_format_p (mp->is_format[i]))
218 {
219 fputs ("/* Flag:", fp);
220 fputs (make_format_description_string (mp->is_format[i],
221 format_language[i], debug),
222 fp);
223 fputs (" */\n", fp);
224 }
225 }
226
227 /* Now write the untranslated string and the translated string. */
228 write_escaped_string (fp, mp->msgid);
229 fputs (" = ", fp);
230 if (mp->msgstr[0] != '\0')
231 {
232 if (mp->is_fuzzy)
233 {
234 /* Output the msgid as value, so that at runtime the untranslated
235 string is returned. */
236 write_escaped_string (fp, mp->msgid);
237
238 /* Output the msgstr as a comment, so that at runtime
239 propertyListFromStringsFileFormat ignores it. */
240 if (c_strstr (mp->msgstr, "*/") == NULL)
241 {
242 fputs (" /* = ", fp);
243 write_escaped_string (fp, mp->msgstr);
244 fputs (" */", fp);
245 }
246 else
247 {
248 fputs ("; // = ", fp);
249 write_escaped_string (fp, mp->msgstr);
250 }
251 }
252 else
253 write_escaped_string (fp, mp->msgstr);
254 }
255 else
256 {
257 /* Output the msgid as value, so that at runtime the untranslated
258 string is returned. */
259 write_escaped_string (fp, mp->msgid);
260 }
261 putc (';', fp);
262
263 putc ('\n', fp);
264 }
265
266 /* Writes an entire message list to the file. */
267 static void
write_stringtable(FILE * fp,message_list_ty * mlp,const char * canon_encoding,size_t page_width,bool debug)268 write_stringtable (FILE *fp, message_list_ty *mlp, const char *canon_encoding,
269 size_t page_width, bool debug)
270 {
271 bool blank_line;
272 size_t j;
273
274 /* Convert the messages to Unicode. */
275 iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
276
277 /* Output the BOM. */
278 if (!is_ascii_message_list (mlp))
279 fputs ("\xef\xbb\xbf", fp);
280
281 /* Loop through the messages. */
282 blank_line = false;
283 for (j = 0; j < mlp->nitems; ++j)
284 {
285 const message_ty *mp = mlp->item[j];
286
287 if (mp->msgid_plural == NULL)
288 {
289 if (blank_line)
290 putc ('\n', fp);
291
292 write_message (fp, mp, page_width, debug);
293
294 blank_line = true;
295 }
296 }
297 }
298
299 /* Output the contents of a PO file in .strings syntax. */
300 static void
msgdomain_list_print_stringtable(msgdomain_list_ty * mdlp,FILE * fp,size_t page_width,bool debug)301 msgdomain_list_print_stringtable (msgdomain_list_ty *mdlp, FILE *fp,
302 size_t page_width, bool debug)
303 {
304 message_list_ty *mlp;
305
306 if (mdlp->nitems == 1)
307 mlp = mdlp->item[0]->messages;
308 else
309 mlp = message_list_alloc (false);
310 write_stringtable (fp, mlp, mdlp->encoding, page_width, debug);
311 }
312
313 /* Describes a PO file in .strings syntax. */
314 const struct catalog_output_format output_format_stringtable =
315 {
316 msgdomain_list_print_stringtable, /* print */
317 true, /* requires_utf8 */
318 false, /* supports_multiple_domains */
319 false, /* supports_contexts */
320 false, /* supports_plurals */
321 false, /* alternative_is_po */
322 false /* alternative_is_java_class */
323 };
324