1/* Writing NeXTstep/GNUstep .strings files. 2 Copyright (C) 2003 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 "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. */ 59static void 60write_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. */ 101static void 102write_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 (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 (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 (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. */ 267static void 268write_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. */ 300void 301msgdomain_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