1 /* Writing tcl/msgcat .msg files. 2 Copyright (C) 2002-2003, 2005, 2007-2009, 2016 Free Software 3 Foundation, Inc. 4 Written by Bruno Haible <bruno@clisp.org>, 2002. 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program. If not, see <https://www.gnu.org/licenses/>. */ 18 19 #ifdef HAVE_CONFIG_H 20 # include <config.h> 21 #endif 22 #include <alloca.h> 23 24 /* Specification. */ 25 #include "write-tcl.h" 26 27 #include <errno.h> 28 #include <stdbool.h> 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 33 #include "error.h" 34 #include "xerror.h" 35 #include "message.h" 36 #include "msgl-iconv.h" 37 #include "msgl-header.h" 38 #include "po-charset.h" 39 #include "xalloc.h" 40 #include "xmalloca.h" 41 #include "concat-filename.h" 42 #include "fwriteerror.h" 43 #include "unistr.h" 44 #include "gettext.h" 45 46 #define _(str) gettext (str) 47 48 49 /* Write a string in Tcl Unicode notation to the given stream. 50 Tcl 8 uses Unicode for its internal string representation. 51 In tcl-8.3.3, the .msg files are read in using the locale dependent 52 encoding. The only way to specify strings in an encoding independent 53 form is the \unnnn notation. Newer tcl versions have this fixed: 54 they read the .msg files in UTF-8 encoding. */ 55 static void write_tcl_string(FILE * stream,const char * str)56 write_tcl_string (FILE *stream, const char *str) 57 { 58 static const char hexdigit[] = "0123456789abcdef"; 59 const char *str_limit = str + strlen (str); 60 61 fprintf (stream, "\""); 62 while (str < str_limit) 63 { 64 ucs4_t uc; 65 unsigned int count; 66 count = u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str); 67 if (uc < 0x10000) 68 { 69 /* Single UCS-2 'char'. */ 70 if (uc == 0x000a) 71 fprintf (stream, "\\n"); 72 else if (uc == 0x000d) 73 fprintf (stream, "\\r"); 74 else if (uc == 0x0022) 75 fprintf (stream, "\\\""); 76 else if (uc == 0x0024) 77 fprintf (stream, "\\$"); 78 else if (uc == 0x005b) 79 fprintf (stream, "\\["); 80 else if (uc == 0x005c) 81 fprintf (stream, "\\\\"); 82 else if (uc == 0x005d) 83 fprintf (stream, "\\]"); 84 /* No need to escape '{' and '}' because we don't have opening 85 braces outside the strings. */ 86 #if 0 87 else if (uc == 0x007b) 88 fprintf (stream, "\\{"); 89 else if (uc == 0x007d) 90 fprintf (stream, "\\}"); 91 #endif 92 else if (uc >= 0x0020 && uc < 0x007f) 93 fprintf (stream, "%c", (int) uc); 94 else 95 fprintf (stream, "\\u%c%c%c%c", 96 hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f], 97 hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]); 98 } 99 else 100 /* The \unnnn notation doesn't support characters >= 0x10000. 101 We output them as UTF-8 byte sequences and hope that either 102 the Tcl version reading them will be new enough or that the 103 user is using an UTF-8 locale. */ 104 fwrite (str, 1, count, stream); 105 str += count; 106 } 107 fprintf (stream, "\""); 108 } 109 110 111 static void write_msg(FILE * output_file,message_list_ty * mlp,const char * locale_name)112 write_msg (FILE *output_file, message_list_ty *mlp, const char *locale_name) 113 { 114 size_t j; 115 116 /* We don't care about esthetic formattic of the output (like respecting 117 a maximum line width, or including the translator comments) because 118 the \unnnn notation is unesthetic anyway. Translators shall edit 119 the PO file. */ 120 for (j = 0; j < mlp->nitems; j++) 121 { 122 message_ty *mp = mlp->item[j]; 123 124 if (is_header (mp)) 125 /* Tcl's msgcat unit ignores this, but msgunfmt needs it. */ 126 fprintf (output_file, "set ::msgcat::header "); 127 else 128 { 129 fprintf (output_file, "::msgcat::mcset %s ", locale_name); 130 write_tcl_string (output_file, mp->msgid); 131 fprintf (output_file, " "); 132 } 133 write_tcl_string (output_file, mp->msgstr); 134 fprintf (output_file, "\n"); 135 } 136 } 137 138 int msgdomain_write_tcl(message_list_ty * mlp,const char * canon_encoding,const char * locale_name,const char * directory)139 msgdomain_write_tcl (message_list_ty *mlp, const char *canon_encoding, 140 const char *locale_name, 141 const char *directory) 142 { 143 /* If no entry for this domain don't even create the file. */ 144 if (mlp->nitems == 0) 145 return 0; 146 147 /* Determine whether mlp has entries with context. */ 148 { 149 bool has_context; 150 size_t j; 151 152 has_context = false; 153 for (j = 0; j < mlp->nitems; j++) 154 if (mlp->item[j]->msgctxt != NULL) 155 has_context = true; 156 if (has_context) 157 { 158 multiline_error (xstrdup (""), 159 xstrdup (_("\ 160 message catalog has context dependent translations\n\ 161 but the Tcl message catalog format doesn't support contexts\n"))); 162 return 1; 163 } 164 } 165 166 /* Determine whether mlp has plural entries. */ 167 { 168 bool has_plural; 169 size_t j; 170 171 has_plural = false; 172 for (j = 0; j < mlp->nitems; j++) 173 if (mlp->item[j]->msgid_plural != NULL) 174 has_plural = true; 175 if (has_plural) 176 { 177 multiline_error (xstrdup (""), 178 xstrdup (_("\ 179 message catalog has plural form translations\n\ 180 but the Tcl message catalog format doesn't support plural handling\n"))); 181 return 1; 182 } 183 } 184 185 /* Convert the messages to Unicode. */ 186 iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL); 187 188 /* Support for "reproducible builds": Delete information that may vary 189 between builds in the same conditions. */ 190 message_list_delete_header_field (mlp, "POT-Creation-Date:"); 191 192 /* Now create the file. */ 193 { 194 size_t len; 195 char *frobbed_locale_name; 196 char *p; 197 char *file_name; 198 FILE *output_file; 199 200 /* Convert the locale name to lowercase and remove any encoding. */ 201 len = strlen (locale_name); 202 frobbed_locale_name = (char *) xmalloca (len + 1); 203 memcpy (frobbed_locale_name, locale_name, len + 1); 204 for (p = frobbed_locale_name; *p != '\0'; p++) 205 if (*p >= 'A' && *p <= 'Z') 206 *p = *p - 'A' + 'a'; 207 else if (*p == '.') 208 { 209 *p = '\0'; 210 break; 211 } 212 213 file_name = xconcatenated_filename (directory, frobbed_locale_name, ".msg"); 214 215 output_file = fopen (file_name, "w"); 216 if (output_file == NULL) 217 { 218 error (0, errno, _("error while opening \"%s\" for writing"), 219 file_name); 220 freea (frobbed_locale_name); 221 return 1; 222 } 223 224 write_msg (output_file, mlp, frobbed_locale_name); 225 226 /* Make sure nothing went wrong. */ 227 if (fwriteerror (output_file)) 228 error (EXIT_FAILURE, errno, _("error while writing \"%s\" file"), 229 file_name); 230 231 freea (frobbed_locale_name); 232 } 233 234 return 0; 235 } 236