(root)/
gettext-0.22.4/
gettext-tools/
src/
write-stringtable.c
       1  /* Writing NeXTstep/GNUstep .strings files.
       2     Copyright (C) 2003, 2006-2008, 2019, 2021 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 <https://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 <textstyle.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 "xvasprintf.h"
      37  #include "write-po.h"
      38  
      39  /* The format of NeXTstep/GNUstep .strings files is documented in
      40       gnustep-base-1.8.0/Tools/make_strings/Using.txt
      41     and in the comments of method propertyListFromStringsFileFormat in
      42       gnustep-base-1.8.0/Source/NSString.m
      43     In summary, it's a Objective-C like file with pseudo-assignments of the form
      44            "key" = "value";
      45     where the key is the msgid and the value is the msgstr.
      46   */
      47  
      48  /* Handling of comments: We copy all comments from the PO file to the
      49     .strings file. This is not really needed; it's a service for translators
      50     who don't like PO files and prefer to maintain the .strings file.  */
      51  
      52  /* Since the interpretation of text files in GNUstep depends on the locale's
      53     encoding if they don't have a BOM, we choose one of three encodings with
      54     a BOM: UCS-2BE, UCS-2LE, UTF-8.  Since the first two of these don't cope
      55     with all of Unicode and we don't know whether GNUstep will switch to
      56     UTF-16 instead of UCS-2, we use UTF-8 with BOM.  BOMs are bad because they
      57     get in the way when concatenating files, but here we have no choice.  */
      58  
      59  /* Writes a key or value to the stream, without newline.  */
      60  static void
      61  write_escaped_string (ostream_t stream, const char *str)
      62  {
      63    const char *str_limit = str + strlen (str);
      64  
      65    ostream_write_str (stream, "\"");
      66    while (str < str_limit)
      67      {
      68        unsigned char c = (unsigned char) *str++;
      69  
      70        if (c == '\t')
      71          ostream_write_str (stream, "\\t");
      72        else if (c == '\n')
      73          ostream_write_str (stream, "\\n");
      74        else if (c == '\r')
      75          ostream_write_str (stream, "\\r");
      76        else if (c == '\f')
      77          ostream_write_str (stream, "\\f");
      78        else if (c == '\\' || c == '"')
      79          {
      80            char seq[2];
      81            seq[0] = '\\';
      82            seq[1] = c;
      83            ostream_write_mem (stream, seq, 2);
      84          }
      85        else
      86          {
      87            char seq[1];
      88            seq[0] = c;
      89            ostream_write_mem (stream, seq, 1);
      90          }
      91      }
      92    ostream_write_str (stream, "\"");
      93  }
      94  
      95  /* Writes a message to the stream.  */
      96  static void
      97  write_message (ostream_t stream, const message_ty *mp,
      98                 size_t page_width, bool debug)
      99  {
     100    /* Print translator comment if available.  */
     101    if (mp->comment != NULL)
     102      {
     103        size_t j;
     104  
     105        for (j = 0; j < mp->comment->nitems; ++j)
     106          {
     107            const char *s = mp->comment->item[j];
     108  
     109            /* Test whether it is safe to output the comment in C style, or
     110               whether we need C++ style for it.  */
     111            if (c_strstr (s, "*/") == NULL)
     112              {
     113                ostream_write_str (stream, "/*");
     114                if (*s != '\0' && *s != '\n')
     115                  ostream_write_str (stream, " ");
     116                ostream_write_str (stream, s);
     117                ostream_write_str (stream, " */\n");
     118              }
     119            else
     120              do
     121                {
     122                  const char *e;
     123                  ostream_write_str (stream, "//");
     124                  if (*s != '\0' && *s != '\n')
     125                    ostream_write_str (stream, " ");
     126                  e = strchr (s, '\n');
     127                  if (e == NULL)
     128                    {
     129                      ostream_write_str (stream, s);
     130                      s = NULL;
     131                    }
     132                  else
     133                    {
     134                      ostream_write_mem (stream, s, e - s);
     135                      s = e + 1;
     136                    }
     137                  ostream_write_str (stream, "\n");
     138                }
     139              while (s != NULL);
     140          }
     141      }
     142  
     143    /* Print xgettext extracted comments.  */
     144    if (mp->comment_dot != NULL)
     145      {
     146        size_t j;
     147  
     148        for (j = 0; j < mp->comment_dot->nitems; ++j)
     149          {
     150            const char *s = mp->comment_dot->item[j];
     151  
     152            /* Test whether it is safe to output the comment in C style, or
     153               whether we need C++ style for it.  */
     154            if (c_strstr (s, "*/") == NULL)
     155              {
     156                ostream_write_str (stream, "/* Comment: ");
     157                ostream_write_str (stream, s);
     158                ostream_write_str (stream, " */\n");
     159              }
     160            else
     161              {
     162                bool first = true;
     163                do
     164                  {
     165                    const char *e;
     166                    ostream_write_str (stream, "//");
     167                    if (first || (*s != '\0' && *s != '\n'))
     168                      ostream_write_str (stream, " ");
     169                    if (first)
     170                      ostream_write_str (stream, "Comment: ");
     171                    e = strchr (s, '\n');
     172                    if (e == NULL)
     173                      {
     174                        ostream_write_str (stream, s);
     175                        s = NULL;
     176                      }
     177                    else
     178                      {
     179                        ostream_write_mem (stream, s, e - s);
     180                        s = e + 1;
     181                      }
     182                    ostream_write_str (stream, "\n");
     183                    first = false;
     184                  }
     185                while (s != NULL);
     186              }
     187          }
     188      }
     189  
     190    /* Print the file position comments.  */
     191    if (mp->filepos_count != 0)
     192      {
     193        size_t j;
     194  
     195        for (j = 0; j < mp->filepos_count; ++j)
     196          {
     197            lex_pos_ty *pp = &mp->filepos[j];
     198            const char *cp = pp->file_name;
     199            char *str;
     200  
     201            while (cp[0] == '.' && cp[1] == '/')
     202              cp += 2;
     203            str = xasprintf ("/* File: %s:%ld */\n", cp, (long) pp->line_number);
     204            ostream_write_str (stream, str);
     205            free (str);
     206          }
     207      }
     208  
     209    /* Print flag information in special comment.  */
     210    if (mp->is_fuzzy || mp->msgstr[0] == '\0')
     211      ostream_write_str (stream, "/* Flag: untranslated */\n");
     212    if (mp->obsolete)
     213      ostream_write_str (stream, "/* Flag: unmatched */\n");
     214    {
     215      size_t i;
     216      for (i = 0; i < NFORMATS; i++)
     217        if (significant_format_p (mp->is_format[i]))
     218          {
     219            ostream_write_str (stream, "/* Flag: ");
     220            ostream_write_str (stream,
     221                               make_format_description_string (mp->is_format[i],
     222                                                               format_language[i],
     223                                                               debug));
     224            ostream_write_str (stream, " */\n");
     225          }
     226    }
     227    if (has_range_p (mp->range))
     228      {
     229        char *string;
     230  
     231        ostream_write_str (stream, "/* Flag: ");
     232        string = make_range_description_string (mp->range);
     233        ostream_write_str (stream, string);
     234        free (string);
     235        ostream_write_str (stream, " */\n");
     236      }
     237  
     238    /* Now write the untranslated string and the translated string.  */
     239    write_escaped_string (stream, mp->msgid);
     240    ostream_write_str (stream, " = ");
     241    if (mp->msgstr[0] != '\0')
     242      {
     243        if (mp->is_fuzzy)
     244          {
     245            /* Output the msgid as value, so that at runtime the untranslated
     246               string is returned.  */
     247            write_escaped_string (stream, mp->msgid);
     248  
     249            /* Output the msgstr as a comment, so that at runtime
     250               propertyListFromStringsFileFormat ignores it.  */
     251            if (c_strstr (mp->msgstr, "*/") == NULL)
     252              {
     253                ostream_write_str (stream, " /* = ");
     254                write_escaped_string (stream, mp->msgstr);
     255                ostream_write_str (stream, " */");
     256              }
     257            else
     258              {
     259                ostream_write_str (stream, "; // = ");
     260                write_escaped_string (stream, mp->msgstr);
     261              }
     262          }
     263        else
     264          write_escaped_string (stream, mp->msgstr);
     265      }
     266    else
     267      {
     268        /* Output the msgid as value, so that at runtime the untranslated
     269           string is returned.  */
     270        write_escaped_string (stream, mp->msgid);
     271      }
     272    ostream_write_str (stream, ";");
     273  
     274    ostream_write_str (stream, "\n");
     275  }
     276  
     277  /* Writes an entire message list to the stream.  */
     278  static void
     279  write_stringtable (ostream_t stream, message_list_ty *mlp,
     280                     const char *canon_encoding, size_t page_width, bool debug)
     281  {
     282    bool blank_line;
     283    size_t j;
     284  
     285    /* Convert the messages to Unicode.  */
     286    iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
     287  
     288    /* Output the BOM.  */
     289    if (!is_ascii_message_list (mlp))
     290      ostream_write_str (stream, "\xef\xbb\xbf");
     291  
     292    /* Loop through the messages.  */
     293    blank_line = false;
     294    for (j = 0; j < mlp->nitems; ++j)
     295      {
     296        const message_ty *mp = mlp->item[j];
     297  
     298        if (mp->msgid_plural == NULL)
     299          {
     300            if (blank_line)
     301              ostream_write_str (stream, "\n");
     302  
     303            write_message (stream, mp, page_width, debug);
     304  
     305            blank_line = true;
     306          }
     307      }
     308  }
     309  
     310  /* Output the contents of a PO file in .strings syntax.  */
     311  static void
     312  msgdomain_list_print_stringtable (msgdomain_list_ty *mdlp, ostream_t stream,
     313                                    size_t page_width, bool debug)
     314  {
     315    message_list_ty *mlp;
     316  
     317    if (mdlp->nitems == 1)
     318      mlp = mdlp->item[0]->messages;
     319    else
     320      mlp = message_list_alloc (false);
     321    write_stringtable (stream, mlp, mdlp->encoding, page_width, debug);
     322  }
     323  
     324  /* Describes a PO file in .strings syntax.  */
     325  const struct catalog_output_format output_format_stringtable =
     326  {
     327    msgdomain_list_print_stringtable,     /* print */
     328    true,                                 /* requires_utf8 */
     329    false,                                /* requires_utf8_for_filenames_with_spaces */
     330    false,                                /* supports_color */
     331    false,                                /* supports_multiple_domains */
     332    false,                                /* supports_contexts */
     333    false,                                /* supports_plurals */
     334    false,                                /* sorts_obsoletes_to_end */
     335    false,                                /* alternative_is_po */
     336    false                                 /* alternative_is_java_class */
     337  };