1/* Writing NeXTstep/GNUstep .strings files. 2 Copyright (C) 2003, 2006-2007 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 <http://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 "message.h" 30#include "msgl-ascii.h" 31#include "msgl-iconv.h" 32#include "po-charset.h" 33#include "c-strstr.h" 34#include "ostream.h" 35#include "xvasprintf.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 stream, without newline. */ 59static void 60write_escaped_string (ostream_t stream, const char *str) 61{ 62 const char *str_limit = str + strlen (str); 63 64 ostream_write_str (stream, "\""); 65 while (str < str_limit) 66 { 67 unsigned char c = (unsigned char) *str++; 68 69 if (c == '\t') 70 ostream_write_str (stream, "\\t"); 71 else if (c == '\n') 72 ostream_write_str (stream, "\\n"); 73 else if (c == '\r') 74 ostream_write_str (stream, "\\r"); 75 else if (c == '\f') 76 ostream_write_str (stream, "\\f"); 77 else if (c == '\\' || c == '"') 78 { 79 char seq[2]; 80 seq[0] = '\\'; 81 seq[1] = c; 82 ostream_write_mem (stream, seq, 2); 83 } 84 else 85 { 86 char seq[1]; 87 seq[0] = c; 88 ostream_write_mem (stream, seq, 1); 89 } 90 } 91 ostream_write_str (stream, "\""); 92} 93 94/* Writes a message to the stream. */ 95static void 96write_message (ostream_t stream, const message_ty *mp, 97 size_t page_width, bool debug) 98{ 99 /* Print translator comment if available. */ 100 if (mp->comment != NULL) 101 { 102 size_t j; 103 104 for (j = 0; j < mp->comment->nitems; ++j) 105 { 106 const char *s = mp->comment->item[j]; 107 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) 111 { 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"); 117 } 118 else 119 do 120 { 121 const char *e; 122 ostream_write_str (stream, "//"); 123 if (*s != '\0' && *s != '\n') 124 ostream_write_str (stream, " "); 125 e = strchr (s, '\n'); 126 if (e == NULL) 127 { 128 ostream_write_str (stream, s); 129 s = NULL; 130 } 131 else 132 { 133 ostream_write_mem (stream, s, e - s); 134 s = e + 1; 135 } 136 ostream_write_str (stream, "\n"); 137 } 138 while (s != NULL); 139 } 140 } 141 142 /* Print xgettext extracted comments. */ 143 if (mp->comment_dot != NULL) 144 { 145 size_t j; 146 147 for (j = 0; j < mp->comment_dot->nitems; ++j) 148 { 149 const char *s = mp->comment_dot->item[j]; 150 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) 154 { 155 ostream_write_str (stream, "/* Comment: "); 156 ostream_write_str (stream, s); 157 ostream_write_str (stream, " */\n"); 158 } 159 else 160 { 161 bool first = true; 162 do 163 { 164 const char *e; 165 ostream_write_str (stream, "//"); 166 if (first || (*s != '\0' && *s != '\n')) 167 ostream_write_str (stream, " "); 168 if (first) 169 ostream_write_str (stream, "Comment: "); 170 e = strchr (s, '\n'); 171 if (e == NULL) 172 { 173 ostream_write_str (stream, s); 174 s = NULL; 175 } 176 else 177 { 178 ostream_write_mem (stream, s, e - s); 179 s = e + 1; 180 } 181 ostream_write_str (stream, "\n"); 182 first = false; 183 } 184 while (s != NULL); 185 } 186 } 187 } 188 189 /* Print the file position comments. */ 190 if (mp->filepos_count != 0) 191 { 192 size_t j; 193 194 for (j = 0; j < mp->filepos_count; ++j) 195 { 196 lex_pos_ty *pp = &mp->filepos[j]; 197 char *cp = pp->file_name; 198 char *str; 199 200 while (cp[0] == '.' && cp[1] == '/') 201 cp += 2; 202 str = xasprintf ("/* File: %s:%ld */\n", cp, (long) pp->line_number); 203 ostream_write_str (stream, str); 204 free (str); 205 } 206 } 207 208 /* Print flag information in special comment. */ 209 if (mp->is_fuzzy || mp->msgstr[0] == '\0') 210 ostream_write_str (stream, "/* Flag: untranslated */\n"); 211 if (mp->obsolete) 212 ostream_write_str (stream, "/* Flag: unmatched */\n"); 213 { 214 size_t i; 215 for (i = 0; i < NFORMATS; i++) 216 if (significant_format_p (mp->is_format[i])) 217 { 218 ostream_write_str (stream, "/* Flag: "); 219 ostream_write_str (stream, 220 make_format_description_string (mp->is_format[i], 221 format_language[i], 222 debug)); 223 ostream_write_str (stream, " */\n"); 224 } 225 } 226 227 /* Now write the untranslated string and the translated string. */ 228 write_escaped_string (stream, mp->msgid); 229 ostream_write_str (stream, " = "); 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 (stream, 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 ostream_write_str (stream, " /* = "); 243 write_escaped_string (stream, mp->msgstr); 244 ostream_write_str (stream, " */"); 245 } 246 else 247 { 248 ostream_write_str (stream, "; // = "); 249 write_escaped_string (stream, mp->msgstr); 250 } 251 } 252 else 253 write_escaped_string (stream, 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 (stream, mp->msgid); 260 } 261 ostream_write_str (stream, ";"); 262 263 ostream_write_str (stream, "\n"); 264} 265 266/* Writes an entire message list to the stream. */ 267static void 268write_stringtable (ostream_t stream, message_list_ty *mlp, 269 const char *canon_encoding, 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 ostream_write_str (stream, "\xef\xbb\xbf"); 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 ostream_write_str (stream, "\n"); 291 292 write_message (stream, mp, page_width, debug); 293 294 blank_line = true; 295 } 296 } 297} 298 299/* Output the contents of a PO file in .strings syntax. */ 300static void 301msgdomain_list_print_stringtable (msgdomain_list_ty *mdlp, ostream_t stream, 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 (stream, mlp, mdlp->encoding, page_width, debug); 311} 312 313/* Describes a PO file in .strings syntax. */ 314const struct catalog_output_format output_format_stringtable = 315{ 316 msgdomain_list_print_stringtable, /* print */ 317 true, /* requires_utf8 */ 318 false, /* supports_color */ 319 false, /* supports_multiple_domains */ 320 false, /* supports_contexts */ 321 false, /* supports_plurals */ 322 false, /* alternative_is_po */ 323 false /* alternative_is_java_class */ 324}; 325