Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wine-mirror
GitHub Repository: wine-mirror/wine
Path: blob/master/tools/wrc/po.c
4389 views
1
/*
2
* Support for po files
3
*
4
* Copyright 2010 Alexandre Julliard
5
*
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.
10
*
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.
15
*
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
19
*/
20
21
#include "config.h"
22
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
32
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"
42
43
static resource_t *new_top, *new_tail;
44
45
struct mo_file
46
{
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 */
53
};
54
55
static int is_english( language_t lan )
56
{
57
return lan == MAKELANGID( LANG_ENGLISH, SUBLANG_DEFAULT );
58
}
59
60
static int is_rtl_language( language_t lan )
61
{
62
return PRIMARYLANGID(lan) == LANG_ARABIC ||
63
PRIMARYLANGID(lan) == LANG_HEBREW ||
64
PRIMARYLANGID(lan) == LANG_PERSIAN;
65
}
66
67
static int uses_larger_font( language_t lan )
68
{
69
return PRIMARYLANGID(lan) == LANG_CHINESE ||
70
PRIMARYLANGID(lan) == LANG_JAPANESE ||
71
PRIMARYLANGID(lan) == LANG_KOREAN;
72
}
73
74
static language_t get_default_sublang( language_t lan )
75
{
76
if (SUBLANGID(lan) != SUBLANG_NEUTRAL) return lan;
77
78
switch (PRIMARYLANGID(lan))
79
{
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 );
86
}
87
}
88
89
static version_t get_dup_version( language_t lang )
90
{
91
/* English "translations" take precedence over the original rc contents */
92
return is_english( lang ) ? 1 : -1;
93
}
94
95
static name_id_t *dup_name_id( name_id_t *id )
96
{
97
name_id_t *new;
98
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;
104
}
105
106
static char *convert_msgid_ascii( const string_t *str, int error_on_invalid_char )
107
{
108
int i;
109
char *buffer = xmalloc( str->size + 1 );
110
111
for (i = 0; i < str->size; i++)
112
{
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)
118
{
119
print_location( &str->loc );
120
error( "Invalid character %04x in source string\n", ch );
121
}
122
free( buffer);
123
return NULL;
124
}
125
buffer[i] = 0;
126
return buffer;
127
}
128
129
static char *get_message_context( char **msgid )
130
{
131
static const char magic[] = "#msgctxt#";
132
char *id, *context;
133
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;
140
}
141
142
static int control_has_title( const control_t *ctrl )
143
{
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)
148
{
149
switch (ctrl->style->or_mask & SS_TYPEMASK)
150
{
151
case SS_LEFT:
152
case SS_CENTER:
153
case SS_RIGHT:
154
return TRUE;
155
default:
156
return FALSE;
157
}
158
}
159
return TRUE;
160
}
161
162
static resource_t *dup_resource( resource_t *res, language_t lang )
163
{
164
resource_t *new = xmalloc( sizeof(*new) );
165
166
*new = *res;
167
new->lan = lang;
168
new->next = new->prev = NULL;
169
new->name = dup_name_id( res->name );
170
171
switch (res->type)
172
{
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);
205
}
206
return new;
207
}
208
209
#ifndef HAVE_LIBGETTEXTPO
210
211
void write_pot_file( const char *outname )
212
{
213
error( "PO files not supported in this wrc build\n" );
214
}
215
216
void write_po_files( const char *outname )
217
{
218
error( "PO files not supported in this wrc build\n" );
219
}
220
221
#else /* HAVE_LIBGETTEXTPO */
222
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 )
226
{
227
fprintf( stderr, "%s:%u:%u: %s\n",
228
filename, (unsigned int)lineno, (unsigned int)column, message_text );
229
if (severity) exit(1);
230
}
231
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 )
238
{
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);
244
}
245
246
static const struct po_xerror_handler po_xerror_handler = { po_xerror, po_xerror2 };
247
248
static po_message_t find_message( po_file_t po, const char *msgid, const char *msgctxt,
249
po_message_iterator_t *iterator )
250
{
251
po_message_t msg;
252
const char *context;
253
254
*iterator = po_message_iterator( po, NULL );
255
while ((msg = po_next_message( *iterator )))
256
{
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;
261
}
262
return msg;
263
}
264
265
static void add_po_string( po_file_t po, const string_t *msgid, const string_t *msgstr, language_t lang )
266
{
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;
272
273
if (!msgid->size) return;
274
275
id_buffer = id = convert_msgid_ascii( msgid, 1 );
276
context = get_message_context( &id );
277
if (context && strcmp(context, dnt) == 0)
278
{
279
/* This string should not be translated */
280
free( id_buffer );
281
return;
282
}
283
284
if (msgstr)
285
{
286
codepage = get_language_codepage( lang );
287
str = str_buffer = convert_string_utf8( msgstr, codepage );
288
if (is_english( lang )) get_message_context( &str );
289
}
290
if (!(msg = find_message( po, id, context, &iterator )))
291
{
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 );
297
}
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 );
302
}
303
304
struct po_file_lang
305
{
306
struct list entry;
307
language_t lang;
308
po_file_t po;
309
};
310
311
static struct list po_file_langs = LIST_INIT( po_file_langs );
312
313
static po_file_t create_po_file(void)
314
{
315
po_file_t po;
316
po_message_t msg;
317
po_message_iterator_t iterator;
318
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;
336
}
337
338
static po_file_t get_po_file( language_t lang )
339
{
340
struct po_file_lang *po_file;
341
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;
344
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;
351
}
352
353
static char *get_po_file_name( language_t lang )
354
{
355
return strmake( "%04x.po", lang );
356
}
357
358
static unsigned int flush_po_files( const char *output_name )
359
{
360
struct po_file_lang *po_file, *next;
361
unsigned int count = 0;
362
363
LIST_FOR_EACH_ENTRY_SAFE( po_file, next, &po_file_langs, struct po_file_lang, entry )
364
{
365
char *name = get_po_file_name( po_file->lang );
366
if (output_name)
367
{
368
if (!strcmp( get_basename(output_name), name ))
369
{
370
po_file_write( po_file->po, name, &po_xerror_handler );
371
count++;
372
}
373
}
374
else /* no specified output name, output a file for every language found */
375
{
376
po_file_write( po_file->po, name, &po_xerror_handler );
377
count++;
378
fprintf( stderr, "created %s\n", name );
379
}
380
po_file_free( po_file->po );
381
list_remove( &po_file->entry );
382
free( po_file );
383
free( name );
384
}
385
return count;
386
}
387
388
static void add_pot_stringtable( po_file_t po, const resource_t *res )
389
{
390
const stringtable_t *stt = res->res.stt;
391
int i;
392
393
while (stt)
394
{
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;
398
}
399
}
400
401
static void add_po_stringtable( const resource_t *english, const resource_t *res )
402
{
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;
407
408
while (english_stt && stt)
409
{
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;
415
}
416
}
417
418
static void add_pot_dialog_controls( po_file_t po, const control_t *ctrl )
419
{
420
while (ctrl)
421
{
422
if (control_has_title( ctrl )) add_po_string( po, ctrl->title->name.s_name, NULL, 0 );
423
ctrl = ctrl->next;
424
}
425
}
426
427
static void add_pot_dialog( po_file_t po, const resource_t *res )
428
{
429
const dialog_t *dlg = res->res.dlg;
430
431
if (dlg->title) add_po_string( po, dlg->title, NULL, 0 );
432
add_pot_dialog_controls( po, dlg->controls );
433
}
434
435
static void compare_dialogs( const dialog_t *english_dlg, const dialog_t *dlg )
436
{
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("??");
441
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 );
446
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;
452
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 );
459
460
if (english_dlg->font || dlg->font)
461
{
462
int size = 0, english_size = 0;
463
char *font = NULL, *english_font = NULL;
464
465
if (english_dlg->font)
466
{
467
english_font = convert_msgid_ascii( english_dlg->font->name, 0 );
468
english_size = english_dlg->font->size;
469
}
470
if (dlg->font)
471
{
472
font = convert_msgid_ascii( dlg->font->name, 0 );
473
size = dlg->font->size;
474
}
475
if (uses_larger_font( dlg->lvc.language )) english_size++;
476
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 );
484
}
485
english_ctrl = english_dlg->controls;
486
ctrl = dlg->controls;
487
for ( ; english_ctrl && ctrl; ctrl = ctrl->next, english_ctrl = english_ctrl->next )
488
{
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 );
493
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 );
502
}
503
free( title );
504
}
505
506
static void add_po_dialog_controls( po_file_t po, const control_t *english_ctrl,
507
const control_t *ctrl, language_t lang )
508
{
509
while (english_ctrl && ctrl)
510
{
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 );
513
514
ctrl = ctrl->next;
515
english_ctrl = english_ctrl->next;
516
}
517
}
518
519
static void add_po_dialog( const resource_t *english, const resource_t *res )
520
{
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 );
524
525
compare_dialogs( english_dlg, dlg );
526
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 );
530
}
531
532
static void add_pot_menu_items( po_file_t po, const menu_item_t *item )
533
{
534
while (item)
535
{
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;
539
}
540
}
541
542
static void add_pot_menu( po_file_t po, const resource_t *res )
543
{
544
add_pot_menu_items( po, res->res.men->items );
545
}
546
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 )
549
{
550
while (english_item && item)
551
{
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;
558
}
559
}
560
561
static void add_po_menu( const resource_t *english, const resource_t *res )
562
{
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 );
566
567
add_po_menu_items( po, english_items, items, res->res.men->lvc.language );
568
}
569
570
static int string_has_context( const string_t *str )
571
{
572
char *id, *id_buffer, *context;
573
574
id_buffer = id = convert_msgid_ascii( str, 1 );
575
context = get_message_context( &id );
576
free( id_buffer );
577
return context != NULL;
578
}
579
580
static void add_pot_accel( po_file_t po, const resource_t *res )
581
{
582
event_t *event = res->res.acc->events;
583
584
while (event)
585
{
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;
590
}
591
}
592
593
static void add_po_accel( const resource_t *english, const resource_t *res )
594
{
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 );
598
599
while (english_event && event)
600
{
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;
605
}
606
}
607
608
static ver_block_t *get_version_langcharset_block( ver_block_t *block )
609
{
610
ver_block_t *stringfileinfo = NULL;
611
char *translation = NULL;
612
ver_value_t *val;
613
614
for (; block; block = block->next)
615
{
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" ))
621
{
622
for (val = block->values; val; val = val->next)
623
{
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 );
632
}
633
}
634
free( name );
635
}
636
637
if (!stringfileinfo || !translation) return NULL;
638
639
for (val = stringfileinfo->values; val; val = val->next)
640
{
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 ))
645
{
646
free( block_name );
647
free( translation );
648
return val->value.block;
649
}
650
free( block_name );
651
}
652
free( translation );
653
return NULL;
654
}
655
656
static int version_value_needs_translation( const ver_value_t *val )
657
{
658
int ret;
659
char *key;
660
661
if (val->type != val_str) return 0;
662
if (!(key = convert_msgid_ascii( val->key, 0 ))) return 0;
663
664
/* most values contain version numbers or file names, only translate a few specific ones */
665
ret = (!strcasecmp( key, "FileDescription" ) || !strcasecmp( key, "ProductName" ));
666
667
free( key );
668
return ret;
669
}
670
671
static void add_pot_versioninfo( po_file_t po, const resource_t *res )
672
{
673
ver_value_t *val;
674
ver_block_t *langcharset = get_version_langcharset_block( res->res.ver->blocks );
675
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 );
680
}
681
682
static void add_po_versioninfo( const resource_t *english, const resource_t *res )
683
{
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 );
688
689
if (!langcharset && !english_langcharset) return;
690
val = langcharset->values;
691
english_val = english_langcharset->values;
692
while (english_val && val)
693
{
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;
698
}
699
}
700
701
static resource_t *find_english_resource( resource_t *res )
702
{
703
resource_t *ptr;
704
705
for (ptr = resource_top; ptr; ptr = ptr->next)
706
{
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;
712
}
713
return NULL;
714
}
715
716
void write_pot_file( const char *outname )
717
{
718
resource_t *res;
719
po_file_t po = create_po_file();
720
721
for (res = resource_top; res; res = res->next)
722
{
723
if (!is_english( res->lan )) continue;
724
725
switch (res->type)
726
{
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;
734
}
735
}
736
po_file_write( po, outname, &po_xerror_handler );
737
po_file_free( po );
738
}
739
740
void write_po_files( const char *outname )
741
{
742
resource_t *res, *english;
743
744
for (res = resource_top; res; res = res->next)
745
{
746
if (!(english = find_english_resource( res ))) continue;
747
switch (res->type)
748
{
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;
756
}
757
}
758
if (!flush_po_files( outname ))
759
{
760
if (outname) error( "No translations found for %s\n", outname );
761
else error( "No translations found\n" );
762
}
763
}
764
765
#endif /* HAVE_LIBGETTEXTPO */
766
767
static struct mo_file *mo_file;
768
769
static void byteswap( unsigned int *data, unsigned int count )
770
{
771
unsigned int i;
772
773
for (i = 0; i < count; i++)
774
data[i] = data[i] >> 24 | (data[i] >> 8 & 0xff00) | (data[i] << 8 & 0xff0000) | data[i] << 24;
775
}
776
777
static void load_mo_file( const char *name )
778
{
779
size_t size;
780
781
if (!(mo_file = read_file( name, &size ))) fatal_perror( "Failed to read %s", name );
782
783
/* sanity checks */
784
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 );
797
798
if (mo_file->magic == 0xde120495)
799
{
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 );
802
}
803
}
804
805
static void free_mo_file(void)
806
{
807
free( mo_file );
808
mo_file = NULL;
809
}
810
811
static inline const char *get_mo_msgid( int index )
812
{
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];
816
}
817
818
static inline const char *get_mo_msgstr( int index )
819
{
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];
823
}
824
825
static const char *get_msgstr( const char *msgid, const char *context, int *found )
826
{
827
int pos, res, min, max;
828
const char *ret = msgid;
829
char *id = NULL;
830
831
if (!mo_file) /* strings containing a context still need to be transformed */
832
{
833
if (context) (*found)++;
834
return ret;
835
}
836
837
if (context) id = strmake( "%s%c%s", context, 4, msgid );
838
min = 0;
839
max = mo_file->count - 1;
840
while (min <= max)
841
{
842
pos = (min + max) / 2;
843
res = strcmp( get_mo_msgid(pos), id ? id : msgid );
844
if (!res)
845
{
846
const char *str = get_mo_msgstr( pos );
847
if (str[0]) /* ignore empty strings */
848
{
849
ret = str;
850
(*found)++;
851
}
852
break;
853
}
854
if (res > 0) max = pos - 1;
855
else min = pos + 1;
856
}
857
free( id );
858
return ret;
859
}
860
861
static string_t *translate_string( string_t *str, int *found )
862
{
863
string_t ustr, *new;
864
const char *transl;
865
char *buffer, *msgid, *context;
866
867
if (!str->size || !(buffer = convert_msgid_ascii( str, 0 )))
868
return convert_string_unicode( str, 1252 );
869
870
msgid = buffer;
871
context = get_message_context( &msgid );
872
transl = get_msgstr( msgid, context, found );
873
874
ustr.type = str_char;
875
ustr.size = strlen( transl );
876
ustr.str.cstr = (char *)transl;
877
ustr.loc = str->loc;
878
879
new = convert_string_unicode( &ustr, CP_UTF8 );
880
free( buffer );
881
return new;
882
}
883
884
static control_t *translate_controls( control_t *ctrl, int *found )
885
{
886
control_t *new, *head = NULL, *tail = NULL;
887
888
while (ctrl)
889
{
890
new = xmalloc( sizeof(*new) );
891
*new = *ctrl;
892
if (control_has_title( ctrl ))
893
{
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 );
897
}
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;
906
}
907
return head;
908
}
909
910
static menu_item_t *translate_items( menu_item_t *item, int *found )
911
{
912
menu_item_t *new, *head = NULL, *tail = NULL;
913
914
while (item)
915
{
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;
926
}
927
return head;
928
}
929
930
static stringtable_t *translate_stringtable( stringtable_t *stt, language_t lang, int *found )
931
{
932
stringtable_t *new, *head = NULL, *tail = NULL;
933
int i;
934
935
while (stt)
936
{
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 );
946
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;
953
}
954
return head;
955
}
956
957
static void translate_dialog( dialog_t *dlg, dialog_t *new, int *found )
958
{
959
if (dlg->title) new->title = translate_string( dlg->title, found );
960
if (is_rtl_language( new->lvc.language ))
961
{
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 );
967
}
968
if (dlg->font)
969
{
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 );
974
}
975
new->controls = translate_controls( dlg->controls, found );
976
}
977
978
static event_t *translate_accel( accelerator_t *acc, accelerator_t *new, int *found )
979
{
980
event_t *event, *new_ev, *head = NULL, *tail = NULL;
981
982
event = acc->events;
983
while (event)
984
{
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;
994
}
995
return head;
996
}
997
998
static ver_value_t *translate_langcharset_values( ver_value_t *val, language_t lang, int *found )
999
{
1000
ver_value_t *new_val, *head = NULL, *tail = NULL;
1001
while (val)
1002
{
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;
1013
}
1014
return head;
1015
}
1016
1017
static ver_value_t *translate_stringfileinfo( ver_value_t *val, language_t lang, int *found )
1018
{
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 );
1024
1025
block_name[0] = strmake( "%04x%04x", langid, 1200 );
1026
block_name[1] = strmake( "%04x%04x", langid, get_language_codepage( lang ));
1027
1028
while (val)
1029
{
1030
new_val = new_ver_value();
1031
*new_val = *val;
1032
if (val->type == val_block)
1033
{
1034
ver_block_t *blk, *blk_head = NULL, *blk_tail = NULL;
1035
for (blk = val->value.block; blk; blk = blk->next)
1036
{
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++)
1043
{
1044
if (!strcasecmp( name, english_block_name[i] ))
1045
{
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 );
1053
}
1054
}
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;
1061
}
1062
new_val->value.block = blk_head;
1063
}
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;
1070
}
1071
1072
for (i = 0; i < ARRAY_SIZE(block_name); i++)
1073
free( block_name[i] );
1074
return head;
1075
}
1076
1077
static ver_value_t *translate_varfileinfo( ver_value_t *val, language_t lang )
1078
{
1079
ver_value_t *new_val, *head = NULL, *tail = NULL;
1080
1081
while (val)
1082
{
1083
new_val = new_ver_value();
1084
*new_val = *val;
1085
if (val->type == val_words)
1086
{
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 ))
1091
{
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 );
1101
}
1102
free( key );
1103
}
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;
1110
}
1111
return head;
1112
}
1113
1114
static ver_block_t *translate_versioninfo( ver_block_t *blk, language_t lang, int *found )
1115
{
1116
ver_block_t *new_blk, *head = NULL, *tail = NULL;
1117
char *name;
1118
1119
while (blk)
1120
{
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;
1135
}
1136
return head;
1137
}
1138
1139
static void translate_resources( language_t lang )
1140
{
1141
resource_t *res;
1142
1143
for (res = resource_top; res; res = res->next)
1144
{
1145
resource_t *new = NULL;
1146
int found = 0;
1147
1148
if (!is_english( res->lan )) continue;
1149
1150
switch (res->type)
1151
{
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;
1177
}
1178
1179
if (new && found)
1180
{
1181
if (new_tail) new_tail->next = new;
1182
else new_top = new;
1183
new->prev = new_tail;
1184
new_tail = new;
1185
}
1186
}
1187
}
1188
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 )
1192
{
1193
static const char sep[] = "_.@";
1194
const char *extra = NULL;
1195
char buffer[LOCALE_NAME_MAX_LENGTH];
1196
char *p, *country = NULL, *modifier = NULL;
1197
1198
if (strlen( unix_name ) >= LOCALE_NAME_MAX_LENGTH) return FALSE;
1199
strcpy( buffer, unix_name );
1200
if (!(p = strpbrk( buffer, sep )))
1201
{
1202
strcpy( win_name, buffer );
1203
return TRUE;
1204
}
1205
1206
if (*p == '_')
1207
{
1208
*p++ = 0;
1209
country = p;
1210
p = strpbrk( p, sep + 1 );
1211
}
1212
if (p && *p == '.')
1213
{
1214
*p++ = 0;
1215
/* charset, ignore */
1216
p = strchr( p, '@' );
1217
}
1218
if (p)
1219
{
1220
*p++ = 0;
1221
modifier = p;
1222
}
1223
1224
/* rebuild a Windows name */
1225
1226
strcpy( win_name, buffer );
1227
if (modifier)
1228
{
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 */
1246
}
1247
if (country)
1248
{
1249
p = win_name + strlen(win_name);
1250
*p++ = '-';
1251
strcpy( p, country );
1252
}
1253
if (extra) strcat( win_name, extra );
1254
return TRUE;
1255
}
1256
1257
1258
void add_translations( const char *po_dir )
1259
{
1260
resource_t *res;
1261
char buffer[256];
1262
char *p, *tok, *name;
1263
FILE *f;
1264
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;
1268
1269
if (!po_dir) /* run through the translation process to remove msg contexts */
1270
{
1271
translate_resources( MAKELANGID( LANG_ENGLISH, SUBLANG_DEFAULT ));
1272
goto done;
1273
}
1274
1275
new_top = new_tail = NULL;
1276
1277
name = strmake( "%s/LINGUAS", po_dir );
1278
if (!(f = fopen( name, "r" )))
1279
{
1280
free( name );
1281
return;
1282
}
1283
free( name );
1284
while (fgets( buffer, sizeof(buffer), f ))
1285
{
1286
if ((p = strchr( buffer, '#' ))) *p = 0;
1287
for (tok = strtok( buffer, " \t\r\n" ); tok; tok = strtok( NULL, " \t\r\n" ))
1288
{
1289
char locale[LOCALE_NAME_MAX_LENGTH];
1290
language_t lang;
1291
1292
if (!unix_to_win_locale( tok, locale ) || !(lang = get_language_from_name( locale )))
1293
error( "unknown language '%s'\n", tok );
1294
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 );
1300
}
1301
}
1302
fclose( f );
1303
1304
done:
1305
/* prepend the translated resources to the global list */
1306
if (new_tail)
1307
{
1308
new_tail->next = resource_top;
1309
resource_top->prev = new_tail;
1310
resource_top = new_top;
1311
}
1312
}
1313
1314