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