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