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