1 /* Writing NeXTstep/GNUstep .strings files.
2 Copyright (C) 2003, 2006-2008, 2015 Free Software Foundation, Inc.
3 Written by Bruno Haible <bruno@clisp.org>, 2003.
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.
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.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>. */
23 #include "write-stringtable.h"
30 #include "msgl-ascii.h"
31 #include "msgl-iconv.h"
32 #include "po-charset.h"
35 #include "xvasprintf.h"
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
44 where the key is the msgid and the value is the msgstr.
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. */
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. */
58 /* Writes a key or value to the stream, without newline. */
60 write_escaped_string (ostream_t stream, const char *str)
62 const char *str_limit = str + strlen (str);
64 ostream_write_str (stream, "\"");
65 while (str < str_limit)
67 unsigned char c = (unsigned char) *str++;
70 ostream_write_str (stream, "\\t");
72 ostream_write_str (stream, "\\n");
74 ostream_write_str (stream, "\\r");
76 ostream_write_str (stream, "\\f");
77 else if (c == '\\' || c == '"')
82 ostream_write_mem (stream, seq, 2);
88 ostream_write_mem (stream, seq, 1);
91 ostream_write_str (stream, "\"");
94 /* Writes a message to the stream. */
96 write_message (ostream_t stream, const message_ty *mp,
97 size_t page_width, bool debug)
99 /* Print translator comment if available. */
100 if (mp->comment != NULL)
104 for (j = 0; j < mp->comment->nitems; ++j)
106 const char *s = mp->comment->item[j];
108 /* Test whether it is safe to output the comment in C style, or
109 whether we need C++ style for it. */
110 if (c_strstr (s, "*/") == NULL)
112 ostream_write_str (stream, "/*");
113 if (*s != '\0' && *s != '\n')
114 ostream_write_str (stream, " ");
115 ostream_write_str (stream, s);
116 ostream_write_str (stream, " */\n");
122 ostream_write_str (stream, "//");
123 if (*s != '\0' && *s != '\n')
124 ostream_write_str (stream, " ");
125 e = strchr (s, '\n');
128 ostream_write_str (stream, s);
133 ostream_write_mem (stream, s, e - s);
136 ostream_write_str (stream, "\n");
142 /* Print xgettext extracted comments. */
143 if (mp->comment_dot != NULL)
147 for (j = 0; j < mp->comment_dot->nitems; ++j)
149 const char *s = mp->comment_dot->item[j];
151 /* Test whether it is safe to output the comment in C style, or
152 whether we need C++ style for it. */
153 if (c_strstr (s, "*/") == NULL)
155 ostream_write_str (stream, "/* Comment: ");
156 ostream_write_str (stream, s);
157 ostream_write_str (stream, " */\n");
165 ostream_write_str (stream, "//");
166 if (first || (*s != '\0' && *s != '\n'))
167 ostream_write_str (stream, " ");
169 ostream_write_str (stream, "Comment: ");
170 e = strchr (s, '\n');
173 ostream_write_str (stream, s);
178 ostream_write_mem (stream, s, e - s);
181 ostream_write_str (stream, "\n");
189 /* Print the file position comments. */
190 if (mp->filepos_count != 0)
194 for (j = 0; j < mp->filepos_count; ++j)
196 lex_pos_ty *pp = &mp->filepos[j];
197 const char *cp = pp->file_name;
200 while (cp[0] == '.' && cp[1] == '/')
202 str = xasprintf ("/* File: %s:%ld */\n", cp, (long) pp->line_number);
203 ostream_write_str (stream, str);
208 /* Print flag information in special comment. */
209 if (mp->is_fuzzy || mp->msgstr[0] == '\0')
210 ostream_write_str (stream, "/* Flag: untranslated */\n");
212 ostream_write_str (stream, "/* Flag: unmatched */\n");
215 for (i = 0; i < NFORMATS; i++)
216 if (significant_format_p (mp->is_format[i]))
218 ostream_write_str (stream, "/* Flag: ");
219 ostream_write_str (stream,
220 make_format_description_string (mp->is_format[i],
223 ostream_write_str (stream, " */\n");
226 if (has_range_p (mp->range))
230 ostream_write_str (stream, "/* Flag: ");
231 string = make_range_description_string (mp->range);
232 ostream_write_str (stream, string);
234 ostream_write_str (stream, " */\n");
237 /* Now write the untranslated string and the translated string. */
238 write_escaped_string (stream, mp->msgid);
239 ostream_write_str (stream, " = ");
240 if (mp->msgstr[0] != '\0')
244 /* Output the msgid as value, so that at runtime the untranslated
245 string is returned. */
246 write_escaped_string (stream, mp->msgid);
248 /* Output the msgstr as a comment, so that at runtime
249 propertyListFromStringsFileFormat ignores it. */
250 if (c_strstr (mp->msgstr, "*/") == NULL)
252 ostream_write_str (stream, " /* = ");
253 write_escaped_string (stream, mp->msgstr);
254 ostream_write_str (stream, " */");
258 ostream_write_str (stream, "; // = ");
259 write_escaped_string (stream, mp->msgstr);
263 write_escaped_string (stream, mp->msgstr);
267 /* Output the msgid as value, so that at runtime the untranslated
268 string is returned. */
269 write_escaped_string (stream, mp->msgid);
271 ostream_write_str (stream, ";");
273 ostream_write_str (stream, "\n");
276 /* Writes an entire message list to the stream. */
278 write_stringtable (ostream_t stream, message_list_ty *mlp,
279 const char *canon_encoding, size_t page_width, bool debug)
284 /* Convert the messages to Unicode. */
285 iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
287 /* Output the BOM. */
288 if (!is_ascii_message_list (mlp))
289 ostream_write_str (stream, "\xef\xbb\xbf");
291 /* Loop through the messages. */
293 for (j = 0; j < mlp->nitems; ++j)
295 const message_ty *mp = mlp->item[j];
297 if (mp->msgid_plural == NULL)
300 ostream_write_str (stream, "\n");
302 write_message (stream, mp, page_width, debug);
309 /* Output the contents of a PO file in .strings syntax. */
311 msgdomain_list_print_stringtable (msgdomain_list_ty *mdlp, ostream_t stream,
312 size_t page_width, bool debug)
314 message_list_ty *mlp;
316 if (mdlp->nitems == 1)
317 mlp = mdlp->item[0]->messages;
319 mlp = message_list_alloc (false);
320 write_stringtable (stream, mlp, mdlp->encoding, page_width, debug);
323 /* Describes a PO file in .strings syntax. */
324 const struct catalog_output_format output_format_stringtable =
326 msgdomain_list_print_stringtable, /* print */
327 true, /* requires_utf8 */
328 false, /* supports_color */
329 false, /* supports_multiple_domains */
330 false, /* supports_contexts */
331 false, /* supports_plurals */
332 false, /* sorts_obsoletes_to_end */
333 false, /* alternative_is_po */
334 false /* alternative_is_java_class */