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
29 #ifdef HAVE_LIBGETTEXTPO
30 #include <gettext-po.h>
41 #include "wine/list.h"
43 static resource_t
*new_top
, *new_tail
;
48 unsigned int revision
;
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
))
81 return MAKELANGID( LANG_SPANISH
, SUBLANG_SPANISH_MODERN
);
83 return MAKELANGID( LANG_CHINESE
, SUBLANG_CHINESE_SIMPLIFIED
);
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
)
99 if (!id
|| id
->type
!= name_str
) return id
;
102 new->name
.s_name
= convert_string_unicode( id
->name
.s_name
, 1252 );
106 static char *convert_msgid_ascii( const string_t
*str
, int error_on_invalid_char
)
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
]);
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
);
129 static char *get_message_context( char **msgid
)
131 static const char magic
[] = "#msgctxt#";
134 if (strncmp( *msgid
, magic
, sizeof(magic
) - 1 )) return NULL
;
135 context
= *msgid
+ sizeof(magic
) - 1;
136 if (!(id
= strchr( context
, '#' ))) return NULL
;
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
)
162 static resource_t
*dup_resource( resource_t
*res
, language_t lang
)
164 resource_t
*new = xmalloc( sizeof(*new) );
168 new->next
= new->prev
= NULL
;
169 new->name
= dup_name_id( res
->name
);
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
);
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
);
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
);
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
);
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
);
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
)
254 *iterator
= po_message_iterator( po
, NULL
);
255 while ((msg
= po_next_message( *iterator
)))
257 if (strcmp( po_message_msgid( msg
), msgid
)) continue;
259 if (!(context
= po_message_msgctxt( msg
))) continue;
260 if (!strcmp( context
, msgctxt
)) break;
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";
269 po_message_iterator_t iterator
;
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 */
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
);
311 static struct list po_file_langs
= LIST_INIT( po_file_langs
);
313 static po_file_t
create_po_file(void)
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
);
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
);
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
);
368 if (!strcmp( get_basename(output_name
), name
))
370 po_file_write( po_file
->po
, name
, &po_xerror_handler
);
374 else /* no specified output name, output a file for every language found */
376 po_file_write( po_file
->po
, name
, &po_xerror_handler
);
378 fprintf( stderr
, "created %s\n", name
);
380 po_file_free( po_file
->po
);
381 list_remove( &po_file
->entry
);
388 static void add_pot_stringtable( po_file_t po
, const resource_t
*res
)
390 const stringtable_t
*stt
= res
->res
.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 );
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
);
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
);
414 english_stt
= english_stt
->next
;
418 static void add_pot_dialog_controls( po_file_t po
, const control_t
*ctrl
)
422 if (control_has_title( ctrl
)) add_po_string( po
, ctrl
->title
->name
.s_name
, NULL
, 0 );
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;
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
;
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
);
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 );
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
);
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
);
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
)
536 if (item
->name
) add_po_string( po
, item
->name
, NULL
, 0 );
537 if (item
->popup
) add_pot_menu_items( po
, item
->popup
);
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
);
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
);
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
;
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 );
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
);
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
;
614 for (; block
; block
= block
->next
)
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] );
637 if (!stringfileinfo
|| !translation
) return NULL
;
639 for (val
= stringfileinfo
->values
; val
; val
= val
->next
)
642 if (val
->type
!= val_block
) continue;
643 block_name
= convert_msgid_ascii( val
->value
.block
->name
, 0 );
644 if (!strcasecmp( block_name
, translation
))
648 return val
->value
.block
;
656 static int version_value_needs_translation( const ver_value_t
*val
)
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" ));
671 static void add_pot_versioninfo( po_file_t po
, const resource_t
*res
)
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
);
697 english_val
= english_val
->next
;
701 static resource_t
*find_english_resource( resource_t
*res
)
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;
716 void write_pot_file( const char *outname
)
719 po_file_t po
= create_po_file();
721 for (res
= resource_top
; res
; res
= res
->next
)
723 if (!is_english( res
->lan
)) continue;
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 */
736 po_file_write( po
, outname
, &po_xerror_handler
);
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;
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 */
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
)
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
)
781 if (!(mo_file
= read_file( name
, &size
))) fatal_perror( "Failed to read %s", name
);
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)
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
;
831 if (!mo_file
) /* strings containing a context still need to be transformed */
833 if (context
) (*found
)++;
837 if (context
) id
= strmake( "%s%c%s", context
, 4, msgid
);
839 max
= mo_file
->count
- 1;
842 pos
= (min
+ max
) / 2;
843 res
= strcmp( get_mo_msgid(pos
), id
? id
: msgid
);
846 const char *str
= get_mo_msgstr( pos
);
847 if (str
[0]) /* ignore empty strings */
854 if (res
> 0) max
= pos
- 1;
861 static string_t
*translate_string( string_t
*str
, int *found
)
865 char *buffer
, *msgid
, *context
;
867 if (!str
->size
|| !(buffer
= convert_msgid_ascii( str
, 0 )))
868 return convert_string_unicode( str
, 1252 );
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
;
879 new = convert_string_unicode( &ustr
, CP_UTF8
);
884 static control_t
*translate_controls( control_t
*ctrl
, int *found
)
886 control_t
*new, *head
= NULL
, *tail
= NULL
;
890 new = xmalloc( sizeof(*new) );
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;
910 static menu_item_t
*translate_items( menu_item_t
*item
, int *found
)
912 menu_item_t
*new, *head
= NULL
, *tail
= NULL
;
916 new = xmalloc( sizeof(*new) );
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;
930 static stringtable_t
*translate_stringtable( stringtable_t
*stt
, language_t lang
, int *found
)
932 stringtable_t
*new, *head
= NULL
, *tail
= NULL
;
937 new = xmalloc( sizeof(*new) );
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;
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
;
964 new->exstyle
= new_style( dlg
->exstyle
->or_mask
| WS_EX_LAYOUTRTL
, dlg
->exstyle
->and_mask
);
966 new->exstyle
= new_style( WS_EX_LAYOUTRTL
, 0 );
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
;
985 new_ev
= new_event();
987 if (event
->str
) new_ev
->str
= translate_string( event
->str
, found
);
988 if (tail
) tail
->next
= new_ev
;
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
;
1003 new_val
= new_ver_value();
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
;
1017 static ver_value_t
*translate_stringfileinfo( ver_value_t
*val
, language_t lang
, int *found
)
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
));
1030 new_val
= new_ver_value();
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
;
1039 new_blk
= new_ver_block();
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
] ))
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
);
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
;
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
;
1072 for (i
= 0; i
< ARRAY_SIZE(block_name
); i
++)
1073 free( block_name
[i
] );
1077 static ver_value_t
*translate_varfileinfo( ver_value_t
*val
, language_t lang
)
1079 ver_value_t
*new_val
, *head
= NULL
, *tail
= NULL
;
1083 new_val
= new_ver_value();
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
;
1094 language_t langid
= get_default_sublang( lang
);
1095 new_words
= new_ver_words( langid
);
1096 if (val
->value
.words
->words
[1] == 1200)
1099 codepage
= get_language_codepage( lang
);
1100 new_val
->value
.words
= add_ver_words( new_words
, codepage
);
1104 if (tail
) tail
->next
= new_val
;
1105 else head
= new_val
;
1106 new_val
->next
= NULL
;
1107 new_val
->prev
= tail
;
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
;
1121 new_blk
= new_ver_block();
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
);
1129 if (tail
) tail
->next
= new_blk
;
1130 else head
= new_blk
;
1131 new_blk
->next
= NULL
;
1132 new_blk
->prev
= tail
;
1139 static void translate_resources( language_t lang
)
1143 for (res
= resource_top
; res
; res
= res
->next
)
1145 resource_t
*new = NULL
;
1148 if (!is_english( res
->lan
)) continue;
1153 new = dup_resource( res
, lang
);
1154 new->res
.acc
->events
= translate_accel( res
->res
.acc
, new->res
.acc
, &found
);
1157 new = dup_resource( res
, lang
);
1158 translate_dialog( res
->res
.dlg
, new->res
.dlg
, &found
);
1161 new = dup_resource( res
, lang
);
1162 new->res
.men
->items
= translate_items( res
->res
.men
->items
, &found
);
1165 new = dup_resource( res
, lang
);
1166 new->res
.stt
= translate_stringtable( res
->res
.stt
, lang
, &found
);
1169 new = dup_resource( res
, lang
);
1170 new->res
.ver
->blocks
= translate_versioninfo( res
->res
.ver
->blocks
, lang
, &found
);
1181 if (new_tail
) new_tail
->next
= new;
1183 new->prev
= new_tail
;
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
);
1210 p
= strpbrk( p
, sep
+ 1 );
1215 /* charset, ignore */
1216 p
= strchr( p
, '@' );
1224 /* rebuild a Windows name */
1226 strcpy( win_name
, buffer
);
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 */
1249 p
= win_name
+ strlen(win_name
);
1251 strcpy( p
, country
);
1253 if (extra
) strcat( win_name
, extra
);
1258 void add_translations( const char *po_dir
)
1262 char *p
, *tok
, *name
;
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;
1269 if (!po_dir
) /* run through the translation process to remove msg contexts */
1271 translate_resources( MAKELANGID( LANG_ENGLISH
, SUBLANG_DEFAULT
));
1275 new_top
= new_tail
= NULL
;
1277 name
= strmake( "%s/LINGUAS", po_dir
);
1278 if (!(f
= fopen( name
, "r" )))
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
];
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
);
1305 /* prepend the translated resources to the global list */
1308 new_tail
->next
= resource_top
;
1309 resource_top
->prev
= new_tail
;
1310 resource_top
= new_top
;