Home | History | Annotate | Line # | Download | only in src
      1 /* Writing NeXTstep/GNUstep .strings files.
      2    Copyright (C) 2003, 2006 Free Software Foundation, Inc.
      3    Written by Bruno Haible <bruno (at) 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
     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
    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
    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
    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