conhost: Fix ctrl-c handling.
[wine.git] / tools / wmc / po.c
blobf7113fcabffc5b1817ac19cd418501d516bb9d44
1 /*
2 * Support for po files
4 * Copyright 2010, 2011 Alexandre Julliard
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library 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 GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 #include "config.h"
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <stdarg.h>
27 #include <assert.h>
28 #include <ctype.h>
29 #ifdef HAVE_GETTEXT_PO_H
30 #include <gettext-po.h>
31 #endif
33 #include "wmc.h"
34 #include "utils.h"
35 #include "lang.h"
36 #include "write.h"
37 #include "windef.h"
39 struct mo_file
41 unsigned int magic;
42 unsigned int revision;
43 unsigned int count;
44 unsigned int msgid_off;
45 unsigned int msgstr_off;
46 /* ... rest of file data here */
49 static struct lan_blk *new_top, *new_tail;
51 static BOOL is_english( int lan )
53 return lan == MAKELANGID( LANG_ENGLISH, SUBLANG_DEFAULT );
56 static char *convert_msgid_ascii( const struct lanmsg *msg, int error_on_invalid_char )
58 int i;
59 char *buffer = xmalloc( msg->len * 4 + 1 );
61 for (i = 0; i < msg->len; i++)
63 buffer[i] = msg->msg[i];
64 if (!msg->msg[i]) break;
65 if (msg->msg[i] >= 32 && msg->msg[i] <= 127) continue;
66 if (msg->msg[i] == '\t' || msg->msg[i] == '\n') continue;
67 if (error_on_invalid_char)
69 fprintf( stderr, "%s:%d: ", msg->file, msg->line );
70 error( "Invalid character %04x in source string\n", msg->msg[i] );
72 free( buffer );
73 return NULL;
75 buffer[i] = 0;
76 return buffer;
79 static char *get_message_context( char **msgid )
81 static const char magic[] = "#msgctxt#";
82 char *id, *context;
84 if (strncmp( *msgid, magic, sizeof(magic) - 1 )) return NULL;
85 context = *msgid + sizeof(magic) - 1;
86 if (!(id = strchr( context, '#' ))) return NULL;
87 *id = 0;
88 *msgid = id + 1;
89 return context;
92 #ifdef HAVE_LIBGETTEXTPO
94 static po_message_t find_message( po_file_t po, const char *msgid, const char *msgctxt,
95 po_message_iterator_t *iterator )
97 po_message_t msg;
98 const char *context;
100 *iterator = po_message_iterator( po, NULL );
101 while ((msg = po_next_message( *iterator )))
103 if (strcmp( po_message_msgid( msg ), msgid )) continue;
104 if (!msgctxt) break;
105 if (!(context = po_message_msgctxt( msg ))) continue;
106 if (!strcmp( context, msgctxt )) break;
108 return msg;
111 static void po_xerror( int severity, po_message_t message,
112 const char *filename, size_t lineno, size_t column,
113 int multiline_p, const char *message_text )
115 fprintf( stderr, "%s:%u:%u: %s\n",
116 filename, (unsigned int)lineno, (unsigned int)column, message_text );
117 if (severity) exit(1);
120 static void po_xerror2( int severity, po_message_t message1,
121 const char *filename1, size_t lineno1, size_t column1,
122 int multiline_p1, const char *message_text1,
123 po_message_t message2,
124 const char *filename2, size_t lineno2, size_t column2,
125 int multiline_p2, const char *message_text2 )
127 fprintf( stderr, "%s:%u:%u: %s\n",
128 filename1, (unsigned int)lineno1, (unsigned int)column1, message_text1 );
129 fprintf( stderr, "%s:%u:%u: %s\n",
130 filename2, (unsigned int)lineno2, (unsigned int)column2, message_text2 );
131 if (severity) exit(1);
134 static const struct po_xerror_handler po_xerror_handler = { po_xerror, po_xerror2 };
136 static void add_po_string( po_file_t po, const struct lanmsg *msgid, const struct lanmsg *msgstr )
138 po_message_t msg;
139 po_message_iterator_t iterator;
140 char *id, *id_buffer, *context, *str = NULL, *str_buffer = NULL;
142 if (msgid->len <= 1) return;
144 id_buffer = id = convert_msgid_ascii( msgid, 1 );
145 context = get_message_context( &id );
147 if (msgstr)
149 int len;
150 str_buffer = str = unicode_to_utf8( msgstr->msg, msgstr->len, &len );
151 if (is_english( msgstr->lan )) get_message_context( &str );
153 if (!(msg = find_message( po, id, context, &iterator )))
155 msg = po_message_create();
156 po_message_set_msgid( msg, id );
157 po_message_set_msgstr( msg, str ? str : "" );
158 if (context) po_message_set_msgctxt( msg, context );
159 po_message_insert( iterator, msg );
161 if (msgid->file) po_message_add_filepos( msg, msgid->file, msgid->line );
162 po_message_iterator_free( iterator );
163 free( id_buffer );
164 free( str_buffer );
167 static po_file_t create_po_file(void)
169 po_file_t po;
170 po_message_t msg;
171 po_message_iterator_t iterator;
173 po = po_file_create();
174 iterator = po_message_iterator( po, NULL );
175 msg = po_message_create();
176 po_message_set_msgid( msg, "" );
177 po_message_set_msgstr( msg,
178 "Project-Id-Version: Wine\n"
179 "Report-Msgid-Bugs-To: https://bugs.winehq.org\n"
180 "POT-Creation-Date: N/A\n"
181 "PO-Revision-Date: N/A\n"
182 "Last-Translator: Automatically generated\n"
183 "Language-Team: none\n"
184 "MIME-Version: 1.0\n"
185 "Content-Type: text/plain; charset=UTF-8\n"
186 "Content-Transfer-Encoding: 8bit\n" );
187 po_message_insert( iterator, msg );
188 po_message_iterator_free( iterator );
189 return po;
192 void write_pot_file( const char *outname )
194 int i, j;
195 struct lan_blk *lbp;
196 po_file_t po = create_po_file();
198 for (lbp = lanblockhead; lbp; lbp = lbp->next)
200 if (!is_english( lbp->lan )) continue;
201 for (i = 0; i < lbp->nblk; i++)
203 struct block *blk = &lbp->blks[i];
204 for (j = 0; j < blk->nmsg; j++) add_po_string( po, blk->msgs[j], NULL );
207 po_file_write( po, outname, &po_xerror_handler );
208 po_file_free( po );
212 #else /* HAVE_LIBGETTEXTPO */
214 void write_pot_file( const char *outname )
216 error( "PO files not supported in this wmc build\n" );
219 #endif
221 static struct mo_file *mo_file;
223 static void byteswap( unsigned int *data, unsigned int count )
225 unsigned int i;
227 for (i = 0; i < count; i++)
228 data[i] = data[i] >> 24 | (data[i] >> 8 & 0xff00) | (data[i] << 8 & 0xff0000) | data[i] << 24;
231 static void load_mo_file( const char *name )
233 size_t size;
235 if (!(mo_file = read_file( name, &size ))) fatal_perror( "Failed to read %s", name );
237 /* sanity checks */
239 if (size < sizeof(*mo_file))
240 error( "%s is not a valid .mo file\n", name );
241 if (mo_file->magic == 0xde120495)
242 byteswap( &mo_file->revision, 4 );
243 else if (mo_file->magic != 0x950412de)
244 error( "%s is not a valid .mo file\n", name );
245 if ((mo_file->revision >> 16) > 1)
246 error( "%s: unsupported file version %x\n", name, mo_file->revision );
247 if (mo_file->msgid_off >= size ||
248 mo_file->msgstr_off >= size ||
249 size < sizeof(*mo_file) + 2 * 8 * mo_file->count)
250 error( "%s: corrupted file\n", name );
252 if (mo_file->magic == 0xde120495)
254 byteswap( (unsigned int *)((char *)mo_file + mo_file->msgid_off), 2 * mo_file->count );
255 byteswap( (unsigned int *)((char *)mo_file + mo_file->msgstr_off), 2 * mo_file->count );
259 static void free_mo_file(void)
261 free( mo_file );
262 mo_file = NULL;
265 static inline const char *get_mo_msgid( int index )
267 const char *base = (const char *)mo_file;
268 const unsigned int *offsets = (const unsigned int *)(base + mo_file->msgid_off);
269 return base + offsets[2 * index + 1];
272 static inline const char *get_mo_msgstr( int index )
274 const char *base = (const char *)mo_file;
275 const unsigned int *offsets = (const unsigned int *)(base + mo_file->msgstr_off);
276 return base + offsets[2 * index + 1];
279 static const char *get_msgstr( const char *msgid, const char *context, int *found )
281 int pos, res, min, max;
282 const char *ret = msgid;
283 char *id = NULL;
285 if (!mo_file) /* strings containing a context still need to be transformed */
287 if (context) (*found)++;
288 return ret;
291 if (context) id = strmake( "%s%c%s", context, 4, msgid );
292 min = 0;
293 max = mo_file->count - 1;
294 while (min <= max)
296 pos = (min + max) / 2;
297 res = strcmp( get_mo_msgid(pos), id ? id : msgid );
298 if (!res)
300 const char *str = get_mo_msgstr( pos );
301 if (str[0]) /* ignore empty strings */
303 ret = str;
304 (*found)++;
306 break;
308 if (res > 0) max = pos - 1;
309 else min = pos + 1;
311 free( id );
312 return ret;
315 static struct lanmsg *translate_string( struct lanmsg *str, int lang, int *found )
317 struct lanmsg *new;
318 const char *transl;
319 char *buffer, *msgid, *context;
321 if (str->len <= 1 || !(buffer = convert_msgid_ascii( str, 0 ))) return str;
323 msgid = buffer;
324 context = get_message_context( &msgid );
325 transl = get_msgstr( msgid, context, found );
327 new = xmalloc( sizeof(*new) );
328 new->lan = lang;
329 new->cp = 0; /* FIXME */
330 new->file = str->file;
331 new->line = str->line;
332 new->msg = utf8_to_unicode( transl, strlen(transl) + 1, &new->len );
333 free( buffer );
334 return new;
337 static void translate_block( struct block *blk, struct block *new, int lang, int *found )
339 int i;
341 new->idlo = blk->idlo;
342 new->idhi = blk->idhi;
343 new->size = 0;
344 new->msgs = xmalloc( blk->nmsg * sizeof(*new->msgs) );
345 new->nmsg = blk->nmsg;
346 for (i = 0; i < blk->nmsg; i++)
348 new->msgs[i] = translate_string( blk->msgs[i], lang, found );
349 new->size += ((2 * new->msgs[i]->len + 3) & ~3) + 4;
353 static void translate_messages( int lang )
355 int i, found;
356 struct lan_blk *lbp, *new;
358 for (lbp = lanblockhead; lbp; lbp = lbp->next)
360 if (!is_english( lbp->lan )) continue;
361 found = 0;
362 new = xmalloc( sizeof(*new) );
363 /* English "translations" take precedence over the original contents */
364 new->version = is_english( lang ) ? 1 : -1;
365 new->lan = lang;
366 new->blks = xmalloc( lbp->nblk * sizeof(*new->blks) );
367 new->nblk = lbp->nblk;
369 for (i = 0; i < lbp->nblk; i++)
370 translate_block( &lbp->blks[i], &new->blks[i], lang, &found );
371 if (found)
373 if (new_tail) new_tail->next = new;
374 else new_top = new;
375 new->prev = new_tail;
376 new_tail = new;
378 else
380 free( new->blks );
381 free( new );
386 /* Unix format is: lang[_country][.charset][@modifier]
387 * Windows format is: lang[-script][-country][_modifier] */
388 static int unix_to_win_locale( const char *unix_name, char *win_name )
390 static const char sep[] = "_.@";
391 const char *extra = NULL;
392 char buffer[LOCALE_NAME_MAX_LENGTH];
393 char *p, *country = NULL, *modifier = NULL;
395 if (strlen( unix_name ) >= LOCALE_NAME_MAX_LENGTH) return FALSE;
396 strcpy( buffer, unix_name );
397 if (!(p = strpbrk( buffer, sep )))
399 strcpy( win_name, buffer );
400 return TRUE;
403 if (*p == '_')
405 *p++ = 0;
406 country = p;
407 p = strpbrk( p, sep + 1 );
409 if (p && *p == '.')
411 *p++ = 0;
412 /* charset, ignore */
413 p = strchr( p, '@' );
415 if (p)
417 *p++ = 0;
418 modifier = p;
421 /* rebuild a Windows name */
423 strcpy( win_name, buffer );
424 if (modifier)
426 if (!strcmp( modifier, "arabic" )) strcat( win_name, "-Arab" );
427 else if (!strcmp( modifier, "chakma" )) strcat( win_name, "-Cakm" );
428 else if (!strcmp( modifier, "cherokee" )) strcat( win_name, "-Cher" );
429 else if (!strcmp( modifier, "cyrillic" )) strcat( win_name, "-Cyrl" );
430 else if (!strcmp( modifier, "devanagari" )) strcat( win_name, "-Deva" );
431 else if (!strcmp( modifier, "gurmukhi" )) strcat( win_name, "-Guru" );
432 else if (!strcmp( modifier, "javanese" )) strcat( win_name, "-Java" );
433 else if (!strcmp( modifier, "latin" )) strcat( win_name, "-Latn" );
434 else if (!strcmp( modifier, "mongolian" )) strcat( win_name, "-Mong" );
435 else if (!strcmp( modifier, "syriac" )) strcat( win_name, "-Syrc" );
436 else if (!strcmp( modifier, "tifinagh" )) strcat( win_name, "-Tfng" );
437 else if (!strcmp( modifier, "tibetan" )) strcat( win_name, "-Tibt" );
438 else if (!strcmp( modifier, "vai" )) strcat( win_name, "-Vaii" );
439 else if (!strcmp( modifier, "yi" )) strcat( win_name, "-Yiii" );
440 else if (!strcmp( modifier, "saaho" )) strcpy( win_name, "ssy" );
441 else if (!strcmp( modifier, "valencia" )) extra = "-valencia";
442 /* ignore unknown modifiers */
444 if (country)
446 p = win_name + strlen(win_name);
447 *p++ = '-';
448 strcpy( p, country );
450 if (extra) strcat( win_name, extra );
451 return TRUE;
455 void add_translations( const char *po_dir )
457 struct lan_blk *lbp;
458 char buffer[256];
459 char *p, *tok, *name;
460 FILE *f;
462 /* first check if we have English resources to translate */
463 for (lbp = lanblockhead; lbp; lbp = lbp->next) if (is_english( lbp->lan )) break;
464 if (!lbp) return;
466 if (!po_dir) /* run through the translation process to remove msg contexts */
468 translate_messages( MAKELANGID( LANG_ENGLISH, SUBLANG_DEFAULT ));
469 goto done;
472 new_top = new_tail = NULL;
474 name = strmake( "%s/LINGUAS", po_dir );
475 if (!(f = fopen( name, "r" ))) return;
476 free( name );
477 while (fgets( buffer, sizeof(buffer), f ))
479 if ((p = strchr( buffer, '#' ))) *p = 0;
480 for (tok = strtok( buffer, " \t\r\n" ); tok; tok = strtok( NULL, " \t\r\n" ))
482 char locale[LOCALE_NAME_MAX_LENGTH];
483 unsigned int lang;
485 if (!unix_to_win_locale( tok, locale ) || !(lang = get_language_from_name( locale )))
487 error( "unknown language '%s'\n", tok );
488 continue;
490 name = strmake( "%s/%s.mo", po_dir, tok );
491 load_mo_file( name );
492 translate_messages( lang );
493 free_mo_file();
494 free( name );
497 fclose( f );
499 done:
500 /* prepend the translated messages to the global list */
501 if (new_tail)
503 new_tail->next = lanblockhead;
504 lanblockhead->prev = new_tail;
505 lanblockhead = new_top;