xref: /netbsd-src/external/gpl2/gettext/dist/gettext-tools/src/write-stringtable.c (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
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