winegstreamer: Synchronize concurrent access to the media source.
[wine.git] / tools / wrc / po.c
blobdf1a7159810f67b8289384456b5c9a9e23e5ecdc
1 /*
2 * Support for po files
4 * Copyright 2010 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_LIBGETTEXTPO
30 #include <gettext-po.h>
31 #endif
33 #include "../tools.h"
34 #include "wrc.h"
35 #include "newstruc.h"
36 #include "utils.h"
37 #include "windef.h"
38 #include "winbase.h"
39 #include "wingdi.h"
40 #include "winuser.h"
41 #include "wine/list.h"
43 static resource_t *new_top, *new_tail;
45 struct mo_file
47 unsigned int magic;
48 unsigned int revision;
49 unsigned int count;
50 unsigned int msgid_off;
51 unsigned int msgstr_off;
52 /* ... rest of file data here */
55 static int is_english( language_t lan )
57 return lan == MAKELANGID( LANG_ENGLISH, SUBLANG_DEFAULT );
60 static int is_rtl_language( language_t lan )
62 return PRIMARYLANGID(lan) == LANG_ARABIC ||
63 PRIMARYLANGID(lan) == LANG_HEBREW ||
64 PRIMARYLANGID(lan) == LANG_PERSIAN;
67 static int uses_larger_font( language_t lan )
69 return PRIMARYLANGID(lan) == LANG_CHINESE ||
70 PRIMARYLANGID(lan) == LANG_JAPANESE ||
71 PRIMARYLANGID(lan) == LANG_KOREAN;
74 static language_t get_default_sublang( language_t lan )
76 if (SUBLANGID(lan) != SUBLANG_NEUTRAL) return lan;
78 switch (PRIMARYLANGID(lan))
80 case LANG_SPANISH:
81 return MAKELANGID( LANG_SPANISH, SUBLANG_SPANISH_MODERN );
82 case LANG_CHINESE:
83 return MAKELANGID( LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED );
84 default:
85 return MAKELANGID( PRIMARYLANGID(lan), SUBLANG_DEFAULT );
89 static version_t get_dup_version( language_t lang )
91 /* English "translations" take precedence over the original rc contents */
92 return is_english( lang ) ? 1 : -1;
95 static name_id_t *dup_name_id( name_id_t *id )
97 name_id_t *new;
99 if (!id || id->type != name_str) return id;
100 new = new_name_id();
101 *new = *id;
102 new->name.s_name = convert_string_unicode( id->name.s_name, 1252 );
103 return new;
106 static char *convert_msgid_ascii( const string_t *str, int error_on_invalid_char )
108 int i;
109 char *buffer = xmalloc( str->size + 1 );
111 for (i = 0; i < str->size; i++)
113 WCHAR ch = (str->type == str_unicode ? str->str.wstr[i] : (unsigned char)str->str.cstr[i]);
114 buffer[i] = ch;
115 if (ch >= 32 && ch <= 127) continue;
116 if (ch == '\t' || ch == '\n') continue;
117 if (error_on_invalid_char)
119 print_location( &str->loc );
120 error( "Invalid character %04x in source string\n", ch );
122 free( buffer);
123 return NULL;
125 buffer[i] = 0;
126 return buffer;
129 static char *get_message_context( char **msgid )
131 static const char magic[] = "#msgctxt#";
132 char *id, *context;
134 if (strncmp( *msgid, magic, sizeof(magic) - 1 )) return NULL;
135 context = *msgid + sizeof(magic) - 1;
136 if (!(id = strchr( context, '#' ))) return NULL;
137 *id = 0;
138 *msgid = id + 1;
139 return context;
142 static int control_has_title( const control_t *ctrl )
144 if (!ctrl->title) return FALSE;
145 if (ctrl->title->type != name_str) return FALSE;
146 /* check for text static control */
147 if (ctrl->ctlclass && ctrl->ctlclass->type == name_ord && ctrl->ctlclass->name.i_name == CT_STATIC)
149 switch (ctrl->style->or_mask & SS_TYPEMASK)
151 case SS_LEFT:
152 case SS_CENTER:
153 case SS_RIGHT:
154 return TRUE;
155 default:
156 return FALSE;
159 return TRUE;
162 static resource_t *dup_resource( resource_t *res, language_t lang )
164 resource_t *new = xmalloc( sizeof(*new) );
166 *new = *res;
167 new->lan = lang;
168 new->next = new->prev = NULL;
169 new->name = dup_name_id( res->name );
171 switch (res->type)
173 case res_acc:
174 new->res.acc = xmalloc( sizeof(*(new)->res.acc) );
175 *new->res.acc = *res->res.acc;
176 new->res.acc->lvc.language = lang;
177 new->res.acc->lvc.version = get_dup_version( lang );
178 break;
179 case res_dlg:
180 new->res.dlg = xmalloc( sizeof(*(new)->res.dlg) );
181 *new->res.dlg = *res->res.dlg;
182 new->res.dlg->lvc.language = lang;
183 new->res.dlg->lvc.version = get_dup_version( lang );
184 break;
185 case res_men:
186 new->res.men = xmalloc( sizeof(*(new)->res.men) );
187 *new->res.men = *res->res.men;
188 new->res.men->lvc.language = lang;
189 new->res.men->lvc.version = get_dup_version( lang );
190 break;
191 case res_stt:
192 new->res.stt = xmalloc( sizeof(*(new)->res.stt) );
193 *new->res.stt = *res->res.stt;
194 new->res.stt->lvc.language = lang;
195 new->res.stt->lvc.version = get_dup_version( lang );
196 break;
197 case res_ver:
198 new->res.ver = xmalloc( sizeof(*(new)->res.ver) );
199 *new->res.ver = *res->res.ver;
200 new->res.ver->lvc.language = lang;
201 new->res.ver->lvc.version = get_dup_version( lang );
202 break;
203 default:
204 assert(0);
206 return new;
209 #ifndef HAVE_LIBGETTEXTPO
211 void write_pot_file( const char *outname )
213 error( "PO files not supported in this wrc build\n" );
216 void write_po_files( const char *outname )
218 error( "PO files not supported in this wrc build\n" );
221 #else /* HAVE_LIBGETTEXTPO */
223 static void po_xerror( int severity, po_message_t message,
224 const char *filename, size_t lineno, size_t column,
225 int multiline_p, const char *message_text )
227 fprintf( stderr, "%s:%u:%u: %s\n",
228 filename, (unsigned int)lineno, (unsigned int)column, message_text );
229 if (severity) exit(1);
232 static void po_xerror2( int severity, po_message_t message1,
233 const char *filename1, size_t lineno1, size_t column1,
234 int multiline_p1, const char *message_text1,
235 po_message_t message2,
236 const char *filename2, size_t lineno2, size_t column2,
237 int multiline_p2, const char *message_text2 )
239 fprintf( stderr, "%s:%u:%u: %s\n",
240 filename1, (unsigned int)lineno1, (unsigned int)column1, message_text1 );
241 fprintf( stderr, "%s:%u:%u: %s\n",
242 filename2, (unsigned int)lineno2, (unsigned int)column2, message_text2 );
243 if (severity) exit(1);
246 static const struct po_xerror_handler po_xerror_handler = { po_xerror, po_xerror2 };
248 static po_message_t find_message( po_file_t po, const char *msgid, const char *msgctxt,
249 po_message_iterator_t *iterator )
251 po_message_t msg;
252 const char *context;
254 *iterator = po_message_iterator( po, NULL );
255 while ((msg = po_next_message( *iterator )))
257 if (strcmp( po_message_msgid( msg ), msgid )) continue;
258 if (!msgctxt) break;
259 if (!(context = po_message_msgctxt( msg ))) continue;
260 if (!strcmp( context, msgctxt )) break;
262 return msg;
265 static void add_po_string( po_file_t po, const string_t *msgid, const string_t *msgstr, language_t lang )
267 static const char dnt[] = "do not translate";
268 po_message_t msg;
269 po_message_iterator_t iterator;
270 int codepage;
271 char *id, *id_buffer, *context, *str = NULL, *str_buffer = NULL;
273 if (!msgid->size) return;
275 id_buffer = id = convert_msgid_ascii( msgid, 1 );
276 context = get_message_context( &id );
277 if (context && strcmp(context, dnt) == 0)
279 /* This string should not be translated */
280 free( id_buffer );
281 return;
284 if (msgstr)
286 codepage = get_language_codepage( lang );
287 str = str_buffer = convert_string_utf8( msgstr, codepage );
288 if (is_english( lang )) get_message_context( &str );
290 if (!(msg = find_message( po, id, context, &iterator )))
292 msg = po_message_create();
293 po_message_set_msgid( msg, id );
294 po_message_set_msgstr( msg, str ? str : "" );
295 if (context) po_message_set_msgctxt( msg, context );
296 po_message_insert( iterator, msg );
298 if (msgid->loc.file) po_message_add_filepos( msg, msgid->loc.file, msgid->loc.line );
299 po_message_iterator_free( iterator );
300 free( id_buffer );
301 free( str_buffer );
304 struct po_file_lang
306 struct list entry;
307 language_t lang;
308 po_file_t po;
311 static struct list po_file_langs = LIST_INIT( po_file_langs );
313 static po_file_t create_po_file(void)
315 po_file_t po;
316 po_message_t msg;
317 po_message_iterator_t iterator;
319 po = po_file_create();
320 iterator = po_message_iterator( po, NULL );
321 msg = po_message_create();
322 po_message_set_msgid( msg, "" );
323 po_message_set_msgstr( msg,
324 "Project-Id-Version: Wine\n"
325 "Report-Msgid-Bugs-To: https://bugs.winehq.org\n"
326 "POT-Creation-Date: N/A\n"
327 "PO-Revision-Date: N/A\n"
328 "Last-Translator: Automatically generated\n"
329 "Language-Team: none\n"
330 "MIME-Version: 1.0\n"
331 "Content-Type: text/plain; charset=UTF-8\n"
332 "Content-Transfer-Encoding: 8bit\n" );
333 po_message_insert( iterator, msg );
334 po_message_iterator_free( iterator );
335 return po;
338 static po_file_t get_po_file( language_t lang )
340 struct po_file_lang *po_file;
342 LIST_FOR_EACH_ENTRY( po_file, &po_file_langs, struct po_file_lang, entry )
343 if (po_file->lang == lang) return po_file->po;
345 /* create a new one */
346 po_file = xmalloc( sizeof(*po_file) );
347 po_file->lang = lang;
348 po_file->po = create_po_file();
349 list_add_tail( &po_file_langs, &po_file->entry );
350 return po_file->po;
353 static char *get_po_file_name( language_t lang )
355 return strmake( "%04x.po", lang );
358 static unsigned int flush_po_files( const char *output_name )
360 struct po_file_lang *po_file, *next;
361 unsigned int count = 0;
363 LIST_FOR_EACH_ENTRY_SAFE( po_file, next, &po_file_langs, struct po_file_lang, entry )
365 char *name = get_po_file_name( po_file->lang );
366 if (output_name)
368 if (!strcmp( get_basename(output_name), name ))
370 po_file_write( po_file->po, name, &po_xerror_handler );
371 count++;
374 else /* no specified output name, output a file for every language found */
376 po_file_write( po_file->po, name, &po_xerror_handler );
377 count++;
378 fprintf( stderr, "created %s\n", name );
380 po_file_free( po_file->po );
381 list_remove( &po_file->entry );
382 free( po_file );
383 free( name );
385 return count;
388 static void add_pot_stringtable( po_file_t po, const resource_t *res )
390 const stringtable_t *stt = res->res.stt;
391 int i;
393 while (stt)
395 for (i = 0; i < stt->nentries; i++)
396 if (stt->entries[i].str) add_po_string( po, stt->entries[i].str, NULL, 0 );
397 stt = stt->next;
401 static void add_po_stringtable( const resource_t *english, const resource_t *res )
403 const stringtable_t *english_stt = english->res.stt;
404 const stringtable_t *stt = res->res.stt;
405 po_file_t po = get_po_file( stt->lvc.language );
406 int i;
408 while (english_stt && stt)
410 for (i = 0; i < stt->nentries; i++)
411 if (english_stt->entries[i].str && stt->entries[i].str)
412 add_po_string( po, english_stt->entries[i].str, stt->entries[i].str, stt->lvc.language );
413 stt = stt->next;
414 english_stt = english_stt->next;
418 static void add_pot_dialog_controls( po_file_t po, const control_t *ctrl )
420 while (ctrl)
422 if (control_has_title( ctrl )) add_po_string( po, ctrl->title->name.s_name, NULL, 0 );
423 ctrl = ctrl->next;
427 static void add_pot_dialog( po_file_t po, const resource_t *res )
429 const dialog_t *dlg = res->res.dlg;
431 if (dlg->title) add_po_string( po, dlg->title, NULL, 0 );
432 add_pot_dialog_controls( po, dlg->controls );
435 static void compare_dialogs( const dialog_t *english_dlg, const dialog_t *dlg )
437 const control_t *english_ctrl, *ctrl;
438 unsigned int style = 0, exstyle = 0, english_style = 0, english_exstyle = 0;
439 char *name;
440 char *title = english_dlg->title ? convert_msgid_ascii( english_dlg->title, 0 ) : xstrdup("??");
442 if (english_dlg->width != dlg->width || english_dlg->height != dlg->height)
443 warning( "%04x: dialog %s doesn't have the same size (%d,%d vs %d,%d)\n",
444 dlg->lvc.language, title, dlg->width, dlg->height,
445 english_dlg->width, english_dlg->height );
447 if (dlg->gotstyle) style = dlg->style->or_mask;
448 if (dlg->gotexstyle) exstyle = dlg->exstyle->or_mask;
449 if (english_dlg->gotstyle) english_style = english_dlg->style->or_mask;
450 if (english_dlg->gotexstyle) english_exstyle = english_dlg->exstyle->or_mask;
451 if (is_rtl_language( dlg->lvc.language )) english_exstyle |= WS_EX_LAYOUTRTL;
453 if (english_style != style)
454 warning( "%04x: dialog %s doesn't have the same style (%08x vs %08x)\n",
455 dlg->lvc.language, title, style, english_style );
456 if (english_exstyle != exstyle)
457 warning( "%04x: dialog %s doesn't have the same exstyle (%08x vs %08x)\n",
458 dlg->lvc.language, title, exstyle, english_exstyle );
460 if (english_dlg->font || dlg->font)
462 int size = 0, english_size = 0;
463 char *font = NULL, *english_font = NULL;
465 if (english_dlg->font)
467 english_font = convert_msgid_ascii( english_dlg->font->name, 0 );
468 english_size = english_dlg->font->size;
470 if (dlg->font)
472 font = convert_msgid_ascii( dlg->font->name, 0 );
473 size = dlg->font->size;
475 if (uses_larger_font( dlg->lvc.language )) english_size++;
477 if (!english_font || !font || strcasecmp( english_font, font ) || english_size != size)
478 warning( "%04x: dialog %s doesn't have the same font (%s %u vs %s %u)\n",
479 dlg->lvc.language, title,
480 english_font ? english_font : "default", english_size,
481 font ? font : "default", size );
482 free( font );
483 free( english_font );
485 english_ctrl = english_dlg->controls;
486 ctrl = dlg->controls;
487 for ( ; english_ctrl && ctrl; ctrl = ctrl->next, english_ctrl = english_ctrl->next )
489 if (control_has_title( english_ctrl ))
490 name = convert_msgid_ascii( english_ctrl->title->name.s_name, 0 );
491 else
492 name = strmake( "%d", ctrl->id );
494 if (english_ctrl->width != ctrl->width || english_ctrl->height != ctrl->height)
495 warning( "%04x: dialog %s control %s doesn't have the same size (%d,%d vs %d,%d)\n",
496 dlg->lvc.language, title, name,
497 ctrl->width, ctrl->height, english_ctrl->width, english_ctrl->height );
498 if (english_ctrl->x != ctrl->x || english_ctrl->y != ctrl->y)
499 warning( "%04x: dialog %s control %s doesn't have the same position (%d,%d vs %d,%d)\n",
500 dlg->lvc.language, title, name, ctrl->x, ctrl->y, english_ctrl->x, english_ctrl->y );
501 free( name );
503 free( title );
506 static void add_po_dialog_controls( po_file_t po, const control_t *english_ctrl,
507 const control_t *ctrl, language_t lang )
509 while (english_ctrl && ctrl)
511 if (control_has_title( english_ctrl ) && control_has_title( ctrl ))
512 add_po_string( po, english_ctrl->title->name.s_name, ctrl->title->name.s_name, lang );
514 ctrl = ctrl->next;
515 english_ctrl = english_ctrl->next;
519 static void add_po_dialog( const resource_t *english, const resource_t *res )
521 const dialog_t *english_dlg = english->res.dlg;
522 const dialog_t *dlg = res->res.dlg;
523 po_file_t po = get_po_file( dlg->lvc.language );
525 compare_dialogs( english_dlg, dlg );
527 if (english_dlg->title && dlg->title)
528 add_po_string( po, english_dlg->title, dlg->title, dlg->lvc.language );
529 add_po_dialog_controls( po, english_dlg->controls, dlg->controls, dlg->lvc.language );
532 static void add_pot_menu_items( po_file_t po, const menu_item_t *item )
534 while (item)
536 if (item->name) add_po_string( po, item->name, NULL, 0 );
537 if (item->popup) add_pot_menu_items( po, item->popup );
538 item = item->next;
542 static void add_pot_menu( po_file_t po, const resource_t *res )
544 add_pot_menu_items( po, res->res.men->items );
547 static void add_po_menu_items( po_file_t po, const menu_item_t *english_item,
548 const menu_item_t *item, language_t lang )
550 while (english_item && item)
552 if (english_item->name && item->name)
553 add_po_string( po, english_item->name, item->name, lang );
554 if (english_item->popup && item->popup)
555 add_po_menu_items( po, english_item->popup, item->popup, lang );
556 item = item->next;
557 english_item = english_item->next;
561 static void add_po_menu( const resource_t *english, const resource_t *res )
563 const menu_item_t *english_items = english->res.men->items;
564 const menu_item_t *items = res->res.men->items;
565 po_file_t po = get_po_file( res->res.men->lvc.language );
567 add_po_menu_items( po, english_items, items, res->res.men->lvc.language );
570 static int string_has_context( const string_t *str )
572 char *id, *id_buffer, *context;
574 id_buffer = id = convert_msgid_ascii( str, 1 );
575 context = get_message_context( &id );
576 free( id_buffer );
577 return context != NULL;
580 static void add_pot_accel( po_file_t po, const resource_t *res )
582 event_t *event = res->res.acc->events;
584 while (event)
586 /* accelerators without a context don't make sense in po files */
587 if (event->str && string_has_context( event->str ))
588 add_po_string( po, event->str, NULL, 0 );
589 event = event->next;
593 static void add_po_accel( const resource_t *english, const resource_t *res )
595 event_t *english_event = english->res.acc->events;
596 event_t *event = res->res.acc->events;
597 po_file_t po = get_po_file( res->res.acc->lvc.language );
599 while (english_event && event)
601 if (english_event->str && event->str && string_has_context( english_event->str ))
602 add_po_string( po, english_event->str, event->str, res->res.acc->lvc.language );
603 event = event->next;
604 english_event = english_event->next;
608 static ver_block_t *get_version_langcharset_block( ver_block_t *block )
610 ver_block_t *stringfileinfo = NULL;
611 char *translation = NULL;
612 ver_value_t *val;
614 for (; block; block = block->next)
616 char *name;
617 name = convert_msgid_ascii( block->name, 0 );
618 if (!strcasecmp( name, "stringfileinfo" ))
619 stringfileinfo = block;
620 else if (!strcasecmp( name, "varfileinfo" ))
622 for (val = block->values; val; val = val->next)
624 char *key = convert_msgid_ascii( val->key, 0 );
625 if (val->type == val_words &&
626 !strcasecmp( key, "Translation" ) &&
627 val->value.words->nwords >= 2)
628 translation = strmake( "%04x%04x",
629 val->value.words->words[0],
630 val->value.words->words[1] );
631 free( key );
634 free( name );
637 if (!stringfileinfo || !translation) return NULL;
639 for (val = stringfileinfo->values; val; val = val->next)
641 char *block_name;
642 if (val->type != val_block) continue;
643 block_name = convert_msgid_ascii( val->value.block->name, 0 );
644 if (!strcasecmp( block_name, translation ))
646 free( block_name );
647 free( translation );
648 return val->value.block;
650 free( block_name );
652 free( translation );
653 return NULL;
656 static int version_value_needs_translation( const ver_value_t *val )
658 int ret;
659 char *key;
661 if (val->type != val_str) return 0;
662 if (!(key = convert_msgid_ascii( val->key, 0 ))) return 0;
664 /* most values contain version numbers or file names, only translate a few specific ones */
665 ret = (!strcasecmp( key, "FileDescription" ) || !strcasecmp( key, "ProductName" ));
667 free( key );
668 return ret;
671 static void add_pot_versioninfo( po_file_t po, const resource_t *res )
673 ver_value_t *val;
674 ver_block_t *langcharset = get_version_langcharset_block( res->res.ver->blocks );
676 if (!langcharset) return;
677 for (val = langcharset->values; val; val = val->next)
678 if (version_value_needs_translation( val ))
679 add_po_string( po, val->value.str, NULL, 0 );
682 static void add_po_versioninfo( const resource_t *english, const resource_t *res )
684 const ver_block_t *langcharset = get_version_langcharset_block( res->res.ver->blocks );
685 const ver_block_t *english_langcharset = get_version_langcharset_block( english->res.ver->blocks );
686 ver_value_t *val, *english_val;
687 po_file_t po = get_po_file( res->res.ver->lvc.language );
689 if (!langcharset && !english_langcharset) return;
690 val = langcharset->values;
691 english_val = english_langcharset->values;
692 while (english_val && val)
694 if (val->type == val_str)
695 add_po_string( po, english_val->value.str, val->value.str, res->res.ver->lvc.language );
696 val = val->next;
697 english_val = english_val->next;
701 static resource_t *find_english_resource( resource_t *res )
703 resource_t *ptr;
705 for (ptr = resource_top; ptr; ptr = ptr->next)
707 if (ptr->type != res->type) continue;
708 if (!ptr->lan) continue;
709 if (!is_english( ptr->lan )) continue;
710 if (compare_name_id( ptr->name, res->name )) continue;
711 return ptr;
713 return NULL;
716 void write_pot_file( const char *outname )
718 resource_t *res;
719 po_file_t po = create_po_file();
721 for (res = resource_top; res; res = res->next)
723 if (!is_english( res->lan )) continue;
725 switch (res->type)
727 case res_acc: add_pot_accel( po, res ); break;
728 case res_dlg: add_pot_dialog( po, res ); break;
729 case res_men: add_pot_menu( po, res ); break;
730 case res_stt: add_pot_stringtable( po, res ); break;
731 case res_ver: add_pot_versioninfo( po, res ); break;
732 case res_msg: break; /* FIXME */
733 default: break;
736 po_file_write( po, outname, &po_xerror_handler );
737 po_file_free( po );
740 void write_po_files( const char *outname )
742 resource_t *res, *english;
744 for (res = resource_top; res; res = res->next)
746 if (!(english = find_english_resource( res ))) continue;
747 switch (res->type)
749 case res_acc: add_po_accel( english, res ); break;
750 case res_dlg: add_po_dialog( english, res ); break;
751 case res_men: add_po_menu( english, res ); break;
752 case res_stt: add_po_stringtable( english, res ); break;
753 case res_ver: add_po_versioninfo( english, res ); break;
754 case res_msg: break; /* FIXME */
755 default: break;
758 if (!flush_po_files( outname ))
760 if (outname) error( "No translations found for %s\n", outname );
761 else error( "No translations found\n" );
765 #endif /* HAVE_LIBGETTEXTPO */
767 static struct mo_file *mo_file;
769 static void byteswap( unsigned int *data, unsigned int count )
771 unsigned int i;
773 for (i = 0; i < count; i++)
774 data[i] = data[i] >> 24 | (data[i] >> 8 & 0xff00) | (data[i] << 8 & 0xff0000) | data[i] << 24;
777 static void load_mo_file( const char *name )
779 size_t size;
781 if (!(mo_file = read_file( name, &size ))) fatal_perror( "Failed to read %s", name );
783 /* sanity checks */
785 if (size < sizeof(*mo_file))
786 error( "%s is not a valid .mo file\n", name );
787 if (mo_file->magic == 0xde120495)
788 byteswap( &mo_file->revision, 4 );
789 else if (mo_file->magic != 0x950412de)
790 error( "%s is not a valid .mo file\n", name );
791 if ((mo_file->revision >> 16) > 1)
792 error( "%s: unsupported file version %x\n", name, mo_file->revision );
793 if (mo_file->msgid_off >= size ||
794 mo_file->msgstr_off >= size ||
795 size < sizeof(*mo_file) + 2 * 8 * mo_file->count)
796 error( "%s: corrupted file\n", name );
798 if (mo_file->magic == 0xde120495)
800 byteswap( (unsigned int *)((char *)mo_file + mo_file->msgid_off), 2 * mo_file->count );
801 byteswap( (unsigned int *)((char *)mo_file + mo_file->msgstr_off), 2 * mo_file->count );
805 static void free_mo_file(void)
807 free( mo_file );
808 mo_file = NULL;
811 static inline const char *get_mo_msgid( int index )
813 const char *base = (const char *)mo_file;
814 const unsigned int *offsets = (const unsigned int *)(base + mo_file->msgid_off);
815 return base + offsets[2 * index + 1];
818 static inline const char *get_mo_msgstr( int index )
820 const char *base = (const char *)mo_file;
821 const unsigned int *offsets = (const unsigned int *)(base + mo_file->msgstr_off);
822 return base + offsets[2 * index + 1];
825 static const char *get_msgstr( const char *msgid, const char *context, int *found )
827 int pos, res, min, max;
828 const char *ret = msgid;
829 char *id = NULL;
831 if (!mo_file) /* strings containing a context still need to be transformed */
833 if (context) (*found)++;
834 return ret;
837 if (context) id = strmake( "%s%c%s", context, 4, msgid );
838 min = 0;
839 max = mo_file->count - 1;
840 while (min <= max)
842 pos = (min + max) / 2;
843 res = strcmp( get_mo_msgid(pos), id ? id : msgid );
844 if (!res)
846 const char *str = get_mo_msgstr( pos );
847 if (str[0]) /* ignore empty strings */
849 ret = str;
850 (*found)++;
852 break;
854 if (res > 0) max = pos - 1;
855 else min = pos + 1;
857 free( id );
858 return ret;
861 static string_t *translate_string( string_t *str, int *found )
863 string_t ustr, *new;
864 const char *transl;
865 char *buffer, *msgid, *context;
867 if (!str->size || !(buffer = convert_msgid_ascii( str, 0 )))
868 return convert_string_unicode( str, 1252 );
870 msgid = buffer;
871 context = get_message_context( &msgid );
872 transl = get_msgstr( msgid, context, found );
874 ustr.type = str_char;
875 ustr.size = strlen( transl );
876 ustr.str.cstr = (char *)transl;
877 ustr.loc = str->loc;
879 new = convert_string_unicode( &ustr, CP_UTF8 );
880 free( buffer );
881 return new;
884 static control_t *translate_controls( control_t *ctrl, int *found )
886 control_t *new, *head = NULL, *tail = NULL;
888 while (ctrl)
890 new = xmalloc( sizeof(*new) );
891 *new = *ctrl;
892 if (control_has_title( ctrl ))
894 new->title = new_name_id();
895 *new->title = *ctrl->title;
896 new->title->name.s_name = translate_string( ctrl->title->name.s_name, found );
898 else new->title = dup_name_id( ctrl->title );
899 new->ctlclass = dup_name_id( ctrl->ctlclass );
900 if (tail) tail->next = new;
901 else head = new;
902 new->next = NULL;
903 new->prev = tail;
904 tail = new;
905 ctrl = ctrl->next;
907 return head;
910 static menu_item_t *translate_items( menu_item_t *item, int *found )
912 menu_item_t *new, *head = NULL, *tail = NULL;
914 while (item)
916 new = xmalloc( sizeof(*new) );
917 *new = *item;
918 if (item->name) new->name = translate_string( item->name, found );
919 if (item->popup) new->popup = translate_items( item->popup, found );
920 if (tail) tail->next = new;
921 else head = new;
922 new->next = NULL;
923 new->prev = tail;
924 tail = new;
925 item = item->next;
927 return head;
930 static stringtable_t *translate_stringtable( stringtable_t *stt, language_t lang, int *found )
932 stringtable_t *new, *head = NULL, *tail = NULL;
933 int i;
935 while (stt)
937 new = xmalloc( sizeof(*new) );
938 *new = *stt;
939 new->lvc.language = lang;
940 new->lvc.version = get_dup_version( lang );
941 new->entries = xmalloc( new->nentries * sizeof(*new->entries) );
942 memcpy( new->entries, stt->entries, new->nentries * sizeof(*new->entries) );
943 for (i = 0; i < stt->nentries; i++)
944 if (stt->entries[i].str)
945 new->entries[i].str = translate_string( stt->entries[i].str, found );
947 if (tail) tail->next = new;
948 else head = new;
949 new->next = NULL;
950 new->prev = tail;
951 tail = new;
952 stt = stt->next;
954 return head;
957 static void translate_dialog( dialog_t *dlg, dialog_t *new, int *found )
959 if (dlg->title) new->title = translate_string( dlg->title, found );
960 if (is_rtl_language( new->lvc.language ))
962 new->gotexstyle = TRUE;
963 if (dlg->gotexstyle)
964 new->exstyle = new_style( dlg->exstyle->or_mask | WS_EX_LAYOUTRTL, dlg->exstyle->and_mask );
965 else
966 new->exstyle = new_style( WS_EX_LAYOUTRTL, 0 );
968 if (dlg->font)
970 new->font = xmalloc( sizeof(*dlg->font) );
971 *new->font = *dlg->font;
972 if (uses_larger_font( new->lvc.language )) new->font->size++;
973 new->font->name = convert_string_unicode( dlg->font->name, 1252 );
975 new->controls = translate_controls( dlg->controls, found );
978 static event_t *translate_accel( accelerator_t *acc, accelerator_t *new, int *found )
980 event_t *event, *new_ev, *head = NULL, *tail = NULL;
982 event = acc->events;
983 while (event)
985 new_ev = new_event();
986 *new_ev = *event;
987 if (event->str) new_ev->str = translate_string( event->str, found );
988 if (tail) tail->next = new_ev;
989 else head = new_ev;
990 new_ev->next = NULL;
991 new_ev->prev = tail;
992 tail = new_ev;
993 event = event->next;
995 return head;
998 static ver_value_t *translate_langcharset_values( ver_value_t *val, language_t lang, int *found )
1000 ver_value_t *new_val, *head = NULL, *tail = NULL;
1001 while (val)
1003 new_val = new_ver_value();
1004 *new_val = *val;
1005 if (val->type == val_str)
1006 new_val->value.str = translate_string( val->value.str, found );
1007 if (tail) tail->next = new_val;
1008 else head = new_val;
1009 new_val->next = NULL;
1010 new_val->prev = tail;
1011 tail = new_val;
1012 val = val->next;
1014 return head;
1017 static ver_value_t *translate_stringfileinfo( ver_value_t *val, language_t lang, int *found )
1019 int i;
1020 ver_value_t *new_val, *head = NULL, *tail = NULL;
1021 const char *english_block_name[2] = { "040904b0", "040904e4" };
1022 char *block_name[2];
1023 language_t langid = get_default_sublang( lang );
1025 block_name[0] = strmake( "%04x%04x", langid, 1200 );
1026 block_name[1] = strmake( "%04x%04x", langid, get_language_codepage( lang ));
1028 while (val)
1030 new_val = new_ver_value();
1031 *new_val = *val;
1032 if (val->type == val_block)
1034 ver_block_t *blk, *blk_head = NULL, *blk_tail = NULL;
1035 for (blk = val->value.block; blk; blk = blk->next)
1037 ver_block_t *new_blk;
1038 char *name;
1039 new_blk = new_ver_block();
1040 *new_blk = *blk;
1041 name = convert_msgid_ascii( blk->name, 0 );
1042 for (i = 0; i < ARRAY_SIZE(block_name); i++)
1044 if (!strcasecmp( name, english_block_name[i] ))
1046 string_t str;
1047 str.type = str_char;
1048 str.size = strlen( block_name[i] ) + 1;
1049 str.str.cstr = block_name[i];
1050 str.loc = blk->name->loc;
1051 new_blk->name = convert_string_unicode( &str, CP_UTF8 );
1052 new_blk->values = translate_langcharset_values( blk->values, lang, found );
1055 free( name );
1056 if (blk_tail) blk_tail->next = new_blk;
1057 else blk_head = new_blk;
1058 new_blk->next = NULL;
1059 new_blk->prev = blk_tail;
1060 blk_tail = new_blk;
1062 new_val->value.block = blk_head;
1064 if (tail) tail->next = new_val;
1065 else head = new_val;
1066 new_val->next = NULL;
1067 new_val->prev = tail;
1068 tail = new_val;
1069 val = val->next;
1072 for (i = 0; i < ARRAY_SIZE(block_name); i++)
1073 free( block_name[i] );
1074 return head;
1077 static ver_value_t *translate_varfileinfo( ver_value_t *val, language_t lang )
1079 ver_value_t *new_val, *head = NULL, *tail = NULL;
1081 while (val)
1083 new_val = new_ver_value();
1084 *new_val = *val;
1085 if (val->type == val_words)
1087 char *key = convert_msgid_ascii( val->key, 0 );
1088 if (!strcasecmp( key, "Translation" ) &&
1089 val->value.words->nwords == 2 &&
1090 val->value.words->words[0] == MAKELANGID( LANG_ENGLISH, SUBLANG_ENGLISH_US ))
1092 ver_words_t *new_words;
1093 int codepage;
1094 language_t langid = get_default_sublang( lang );
1095 new_words = new_ver_words( langid );
1096 if (val->value.words->words[1] == 1200)
1097 codepage = 1200;
1098 else
1099 codepage = get_language_codepage( lang );
1100 new_val->value.words = add_ver_words( new_words, codepage );
1102 free( key );
1104 if (tail) tail->next = new_val;
1105 else head = new_val;
1106 new_val->next = NULL;
1107 new_val->prev = tail;
1108 tail = new_val;
1109 val = val->next;
1111 return head;
1114 static ver_block_t *translate_versioninfo( ver_block_t *blk, language_t lang, int *found )
1116 ver_block_t *new_blk, *head = NULL, *tail = NULL;
1117 char *name;
1119 while (blk)
1121 new_blk = new_ver_block();
1122 *new_blk = *blk;
1123 name = convert_msgid_ascii( blk->name, 0 );
1124 if (!strcasecmp( name, "stringfileinfo" ))
1125 new_blk->values = translate_stringfileinfo( blk->values, lang, found );
1126 else if (!strcasecmp( name, "varfileinfo" ))
1127 new_blk->values = translate_varfileinfo( blk->values, lang );
1128 free(name);
1129 if (tail) tail->next = new_blk;
1130 else head = new_blk;
1131 new_blk->next = NULL;
1132 new_blk->prev = tail;
1133 tail = new_blk;
1134 blk = blk->next;
1136 return head;
1139 static void translate_resources( language_t lang )
1141 resource_t *res;
1143 for (res = resource_top; res; res = res->next)
1145 resource_t *new = NULL;
1146 int found = 0;
1148 if (!is_english( res->lan )) continue;
1150 switch (res->type)
1152 case res_acc:
1153 new = dup_resource( res, lang );
1154 new->res.acc->events = translate_accel( res->res.acc, new->res.acc, &found );
1155 break;
1156 case res_dlg:
1157 new = dup_resource( res, lang );
1158 translate_dialog( res->res.dlg, new->res.dlg, &found );
1159 break;
1160 case res_men:
1161 new = dup_resource( res, lang );
1162 new->res.men->items = translate_items( res->res.men->items, &found );
1163 break;
1164 case res_stt:
1165 new = dup_resource( res, lang );
1166 new->res.stt = translate_stringtable( res->res.stt, lang, &found );
1167 break;
1168 case res_ver:
1169 new = dup_resource( res, lang );
1170 new->res.ver->blocks = translate_versioninfo( res->res.ver->blocks, lang, &found );
1171 break;
1172 case res_msg:
1173 /* FIXME */
1174 break;
1175 default:
1176 break;
1179 if (new && found)
1181 if (new_tail) new_tail->next = new;
1182 else new_top = new;
1183 new->prev = new_tail;
1184 new_tail = new;
1189 /* Unix format is: lang[_country][.charset][@modifier]
1190 * Windows format is: lang[-script][-country][_modifier] */
1191 static int unix_to_win_locale( const char *unix_name, char *win_name )
1193 static const char sep[] = "_.@";
1194 const char *extra = NULL;
1195 char buffer[LOCALE_NAME_MAX_LENGTH];
1196 char *p, *country = NULL, *modifier = NULL;
1198 if (strlen( unix_name ) >= LOCALE_NAME_MAX_LENGTH) return FALSE;
1199 strcpy( buffer, unix_name );
1200 if (!(p = strpbrk( buffer, sep )))
1202 strcpy( win_name, buffer );
1203 return TRUE;
1206 if (*p == '_')
1208 *p++ = 0;
1209 country = p;
1210 p = strpbrk( p, sep + 1 );
1212 if (p && *p == '.')
1214 *p++ = 0;
1215 /* charset, ignore */
1216 p = strchr( p, '@' );
1218 if (p)
1220 *p++ = 0;
1221 modifier = p;
1224 /* rebuild a Windows name */
1226 strcpy( win_name, buffer );
1227 if (modifier)
1229 if (!strcmp( modifier, "arabic" )) strcat( win_name, "-Arab" );
1230 else if (!strcmp( modifier, "chakma" )) strcat( win_name, "-Cakm" );
1231 else if (!strcmp( modifier, "cherokee" )) strcat( win_name, "-Cher" );
1232 else if (!strcmp( modifier, "cyrillic" )) strcat( win_name, "-Cyrl" );
1233 else if (!strcmp( modifier, "devanagari" )) strcat( win_name, "-Deva" );
1234 else if (!strcmp( modifier, "gurmukhi" )) strcat( win_name, "-Guru" );
1235 else if (!strcmp( modifier, "javanese" )) strcat( win_name, "-Java" );
1236 else if (!strcmp( modifier, "latin" )) strcat( win_name, "-Latn" );
1237 else if (!strcmp( modifier, "mongolian" )) strcat( win_name, "-Mong" );
1238 else if (!strcmp( modifier, "syriac" )) strcat( win_name, "-Syrc" );
1239 else if (!strcmp( modifier, "tifinagh" )) strcat( win_name, "-Tfng" );
1240 else if (!strcmp( modifier, "tibetan" )) strcat( win_name, "-Tibt" );
1241 else if (!strcmp( modifier, "vai" )) strcat( win_name, "-Vaii" );
1242 else if (!strcmp( modifier, "yi" )) strcat( win_name, "-Yiii" );
1243 else if (!strcmp( modifier, "saaho" )) strcpy( win_name, "ssy" );
1244 else if (!strcmp( modifier, "valencia" )) extra = "-valencia";
1245 /* ignore unknown modifiers */
1247 if (country)
1249 p = win_name + strlen(win_name);
1250 *p++ = '-';
1251 strcpy( p, country );
1253 if (extra) strcat( win_name, extra );
1254 return TRUE;
1258 void add_translations( const char *po_dir )
1260 resource_t *res;
1261 char buffer[256];
1262 char *p, *tok, *name;
1263 FILE *f;
1265 /* first check if we have English resources to translate */
1266 for (res = resource_top; res; res = res->next) if (is_english( res->lan )) break;
1267 if (!res) return;
1269 if (!po_dir) /* run through the translation process to remove msg contexts */
1271 translate_resources( MAKELANGID( LANG_ENGLISH, SUBLANG_DEFAULT ));
1272 goto done;
1275 new_top = new_tail = NULL;
1277 name = strmake( "%s/LINGUAS", po_dir );
1278 if (!(f = fopen( name, "r" )))
1280 free( name );
1281 return;
1283 free( name );
1284 while (fgets( buffer, sizeof(buffer), f ))
1286 if ((p = strchr( buffer, '#' ))) *p = 0;
1287 for (tok = strtok( buffer, " \t\r\n" ); tok; tok = strtok( NULL, " \t\r\n" ))
1289 char locale[LOCALE_NAME_MAX_LENGTH];
1290 language_t lang;
1292 if (!unix_to_win_locale( tok, locale ) || !(lang = get_language_from_name( locale )))
1293 error( "unknown language '%s'\n", tok );
1295 name = strmake( "%s/%s.mo", po_dir, tok );
1296 load_mo_file( name );
1297 translate_resources( lang );
1298 free_mo_file();
1299 free( name );
1302 fclose( f );
1304 done:
1305 /* prepend the translated resources to the global list */
1306 if (new_tail)
1308 new_tail->next = resource_top;
1309 resource_top->prev = new_tail;
1310 resource_top = new_top;