msxml3: Ignore XML property ResolveExternals.
[wine] / tools / wrc / po.c
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_GETTEXT_PO_H
30 #include <gettext-po.h>
31 #endif
32
33 #include "wrc.h"
34 #include "genres.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 #include "wine/unicode.h"
43
44 #ifdef HAVE_LIBGETTEXTPO
45
46 static const struct
47 {
48     unsigned int id, sub;
49     const char *name;
50 } languages[] =
51 {
52     { LANG_ARABIC,         SUBLANG_NEUTRAL,                     "ar" },
53     { LANG_ARABIC,         SUBLANG_ARABIC_SAUDI_ARABIA,         "ar_SA" },
54     { LANG_ARABIC,         SUBLANG_ARABIC_IRAQ,                 "ar_IQ" },
55     { LANG_ARABIC,         SUBLANG_ARABIC_EGYPT,                "ar_EG" },
56     { LANG_ARABIC,         SUBLANG_ARABIC_LIBYA,                "ar_LY" },
57     { LANG_ARABIC,         SUBLANG_ARABIC_ALGERIA,              "ar_DZ" },
58     { LANG_ARABIC,         SUBLANG_ARABIC_MOROCCO,              "ar_MA" },
59     { LANG_ARABIC,         SUBLANG_ARABIC_TUNISIA,              "ar_TN" },
60     { LANG_ARABIC,         SUBLANG_ARABIC_OMAN,                 "ar_OM" },
61     { LANG_ARABIC,         SUBLANG_ARABIC_YEMEN,                "ar_YE" },
62     { LANG_ARABIC,         SUBLANG_ARABIC_SYRIA,                "ar_SY" },
63     { LANG_ARABIC,         SUBLANG_ARABIC_JORDAN,               "ar_JO" },
64     { LANG_ARABIC,         SUBLANG_ARABIC_LEBANON,              "ar_LB" },
65     { LANG_ARABIC,         SUBLANG_ARABIC_KUWAIT,               "ar_KW" },
66     { LANG_ARABIC,         SUBLANG_ARABIC_UAE,                  "ar_AE" },
67     { LANG_ARABIC,         SUBLANG_ARABIC_BAHRAIN,              "ar_BH" },
68     { LANG_ARABIC,         SUBLANG_ARABIC_QATAR,                "ar_QA" },
69     { LANG_BULGARIAN,      SUBLANG_NEUTRAL,                     "bg" },
70     { LANG_BULGARIAN,      SUBLANG_BULGARIAN_BULGARIA,          "bg_BG" },
71     { LANG_CATALAN,        SUBLANG_NEUTRAL,                     "ca" },
72     { LANG_CATALAN,        SUBLANG_CATALAN_CATALAN,             "ca_ES" },
73     { LANG_CHINESE,        SUBLANG_NEUTRAL,                     "zh" },
74     { LANG_CHINESE,        SUBLANG_CHINESE_TRADITIONAL,         "zh_TW" },
75     { LANG_CHINESE,        SUBLANG_CHINESE_SIMPLIFIED,          "zh_CN" },
76     { LANG_CHINESE,        SUBLANG_CHINESE_HONGKONG,            "zh_HK" },
77     { LANG_CHINESE,        SUBLANG_CHINESE_SINGAPORE,           "zh_SG" },
78     { LANG_CHINESE,        SUBLANG_CHINESE_MACAU,               "zh_MO" },
79     { LANG_CZECH,          SUBLANG_NEUTRAL,                     "cs" },
80     { LANG_CZECH,          SUBLANG_CZECH_CZECH_REPUBLIC,        "cs_CZ" },
81     { LANG_DANISH,         SUBLANG_NEUTRAL,                     "da" },
82     { LANG_DANISH,         SUBLANG_DANISH_DENMARK,              "da_DK" },
83     { LANG_GERMAN,         SUBLANG_NEUTRAL,                     "de" },
84     { LANG_GERMAN,         SUBLANG_GERMAN,                      "de_DE" },
85     { LANG_GERMAN,         SUBLANG_GERMAN_SWISS,                "de_CH" },
86     { LANG_GERMAN,         SUBLANG_GERMAN_AUSTRIAN,             "de_AT" },
87     { LANG_GERMAN,         SUBLANG_GERMAN_LUXEMBOURG,           "de_LU" },
88     { LANG_GERMAN,         SUBLANG_GERMAN_LIECHTENSTEIN,        "de_LI" },
89     { LANG_GREEK,          SUBLANG_NEUTRAL,                     "el" },
90     { LANG_GREEK,          SUBLANG_GREEK_GREECE,                "el_GR" },
91     { LANG_ENGLISH,        SUBLANG_NEUTRAL,                     "en" },
92     { LANG_ENGLISH,        SUBLANG_ENGLISH_US,                  "en_US" },
93     { LANG_ENGLISH,        SUBLANG_ENGLISH_UK,                  "en_GB" },
94     { LANG_ENGLISH,        SUBLANG_ENGLISH_AUS,                 "en_AU" },
95     { LANG_ENGLISH,        SUBLANG_ENGLISH_CAN,                 "en_CA" },
96     { LANG_ENGLISH,        SUBLANG_ENGLISH_NZ,                  "en_NZ" },
97     { LANG_ENGLISH,        SUBLANG_ENGLISH_EIRE,                "en_IE" },
98     { LANG_ENGLISH,        SUBLANG_ENGLISH_SOUTH_AFRICA,        "en_ZA" },
99     { LANG_ENGLISH,        SUBLANG_ENGLISH_JAMAICA,             "en_JM" },
100     { LANG_ENGLISH,        SUBLANG_ENGLISH_CARIBBEAN,           "en_CB" },
101     { LANG_ENGLISH,        SUBLANG_ENGLISH_BELIZE,              "en_BZ" },
102     { LANG_ENGLISH,        SUBLANG_ENGLISH_TRINIDAD,            "en_TT" },
103     { LANG_ENGLISH,        SUBLANG_ENGLISH_ZIMBABWE,            "en_ZW" },
104     { LANG_ENGLISH,        SUBLANG_ENGLISH_PHILIPPINES,         "en_PH" },
105     { LANG_SPANISH,        SUBLANG_NEUTRAL,                     "es" },
106     { LANG_SPANISH,        SUBLANG_SPANISH,                     "es_ES" },
107     { LANG_SPANISH,        SUBLANG_SPANISH_MEXICAN,             "es_MX" },
108     { LANG_SPANISH,        SUBLANG_SPANISH_MODERN,              "es_ES_modern" },
109     { LANG_SPANISH,        SUBLANG_SPANISH_GUATEMALA,           "es_GT" },
110     { LANG_SPANISH,        SUBLANG_SPANISH_COSTA_RICA,          "es_CR" },
111     { LANG_SPANISH,        SUBLANG_SPANISH_PANAMA,              "es_PA" },
112     { LANG_SPANISH,        SUBLANG_SPANISH_DOMINICAN_REPUBLIC,  "es_DO" },
113     { LANG_SPANISH,        SUBLANG_SPANISH_VENEZUELA,           "es_VE" },
114     { LANG_SPANISH,        SUBLANG_SPANISH_COLOMBIA,            "es_CO" },
115     { LANG_SPANISH,        SUBLANG_SPANISH_PERU,                "es_PE" },
116     { LANG_SPANISH,        SUBLANG_SPANISH_ARGENTINA,           "es_AR" },
117     { LANG_SPANISH,        SUBLANG_SPANISH_ECUADOR,             "es_EC" },
118     { LANG_SPANISH,        SUBLANG_SPANISH_CHILE,               "es_CL" },
119     { LANG_SPANISH,        SUBLANG_SPANISH_URUGUAY,             "es_UY" },
120     { LANG_SPANISH,        SUBLANG_SPANISH_PARAGUAY,            "es_PY" },
121     { LANG_SPANISH,        SUBLANG_SPANISH_BOLIVIA,             "es_BO" },
122     { LANG_SPANISH,        SUBLANG_SPANISH_EL_SALVADOR,         "es_SV" },
123     { LANG_SPANISH,        SUBLANG_SPANISH_HONDURAS,            "es_HN" },
124     { LANG_SPANISH,        SUBLANG_SPANISH_NICARAGUA,           "es_NI" },
125     { LANG_SPANISH,        SUBLANG_SPANISH_PUERTO_RICO,         "es_PR" },
126     { LANG_FINNISH,        SUBLANG_NEUTRAL,                     "fi" },
127     { LANG_FINNISH,        SUBLANG_FINNISH_FINLAND,             "fi_FI" },
128     { LANG_FRENCH,         SUBLANG_NEUTRAL,                     "fr" },
129     { LANG_FRENCH,         SUBLANG_FRENCH,                      "fr_FR" },
130     { LANG_FRENCH,         SUBLANG_FRENCH_BELGIAN,              "fr_BE" },
131     { LANG_FRENCH,         SUBLANG_FRENCH_CANADIAN,             "fr_CA" },
132     { LANG_FRENCH,         SUBLANG_FRENCH_SWISS,                "fr_CH" },
133     { LANG_FRENCH,         SUBLANG_FRENCH_LUXEMBOURG,           "fr_LU" },
134     { LANG_FRENCH,         SUBLANG_FRENCH_MONACO,               "fr_MC" },
135     { LANG_HEBREW,         SUBLANG_NEUTRAL,                     "he" },
136     { LANG_HEBREW,         SUBLANG_HEBREW_ISRAEL,               "he_IL" },
137     { LANG_HUNGARIAN,      SUBLANG_NEUTRAL,                     "hu" },
138     { LANG_HUNGARIAN,      SUBLANG_HUNGARIAN_HUNGARY,           "hu_HU" },
139     { LANG_ICELANDIC,      SUBLANG_NEUTRAL,                     "is" },
140     { LANG_ICELANDIC,      SUBLANG_ICELANDIC_ICELAND,           "is_IS" },
141     { LANG_ITALIAN,        SUBLANG_NEUTRAL,                     "it" },
142     { LANG_ITALIAN,        SUBLANG_ITALIAN,                     "it_IT" },
143     { LANG_ITALIAN,        SUBLANG_ITALIAN_SWISS,               "it_CH" },
144     { LANG_JAPANESE,       SUBLANG_NEUTRAL,                     "ja" },
145     { LANG_JAPANESE,       SUBLANG_JAPANESE_JAPAN,              "ja_JP" },
146     { LANG_KOREAN,         SUBLANG_NEUTRAL,                     "ko" },
147     { LANG_KOREAN,         SUBLANG_KOREAN,                      "ko_KR" },
148     { LANG_DUTCH,          SUBLANG_NEUTRAL,                     "nl" },
149     { LANG_DUTCH,          SUBLANG_DUTCH,                       "nl_NL" },
150     { LANG_DUTCH,          SUBLANG_DUTCH_BELGIAN,               "nl_BE" },
151     { LANG_DUTCH,          SUBLANG_DUTCH_SURINAM,               "nl_SR" },
152     { LANG_NORWEGIAN,      SUBLANG_NORWEGIAN_BOKMAL,            "nb_NO" },
153     { LANG_NORWEGIAN,      SUBLANG_NORWEGIAN_NYNORSK,           "nn_NO" },
154     { LANG_POLISH,         SUBLANG_NEUTRAL,                     "pl" },
155     { LANG_POLISH,         SUBLANG_POLISH_POLAND,               "pl_PL" },
156     { LANG_PORTUGUESE,     SUBLANG_NEUTRAL,                     "pt" },
157     { LANG_PORTUGUESE,     SUBLANG_PORTUGUESE_BRAZILIAN,        "pt_BR" },
158     { LANG_PORTUGUESE,     SUBLANG_PORTUGUESE_PORTUGAL,         "pt_PT" },
159     { LANG_ROMANSH,        SUBLANG_NEUTRAL,                     "rm" },
160     { LANG_ROMANSH,        SUBLANG_ROMANSH_SWITZERLAND,         "rm_CH" },
161     { LANG_ROMANIAN,       SUBLANG_NEUTRAL,                     "ro" },
162     { LANG_ROMANIAN,       SUBLANG_ROMANIAN_ROMANIA,            "ro_RO" },
163     { LANG_RUSSIAN,        SUBLANG_NEUTRAL,                     "ru" },
164     { LANG_RUSSIAN,        SUBLANG_RUSSIAN_RUSSIA,              "ru_RU" },
165     { LANG_SERBIAN,        SUBLANG_NEUTRAL,                     "hr" },
166     { LANG_SERBIAN,        SUBLANG_SERBIAN_CROATIA,             "hr_HR" },
167     { LANG_SERBIAN,        SUBLANG_SERBIAN_LATIN,               "sr_RS@latin" },
168     { LANG_SERBIAN,        SUBLANG_SERBIAN_CYRILLIC,            "sr_RS@cyrillic" },
169     { LANG_SLOVAK,         SUBLANG_NEUTRAL,                     "sk" },
170     { LANG_SLOVAK,         SUBLANG_SLOVAK_SLOVAKIA,             "sk_SK" },
171     { LANG_ALBANIAN,       SUBLANG_NEUTRAL,                     "sq" },
172     { LANG_ALBANIAN,       SUBLANG_ALBANIAN_ALBANIA,            "sq_AL" },
173     { LANG_SWEDISH,        SUBLANG_NEUTRAL,                     "sv" },
174     { LANG_SWEDISH,        SUBLANG_SWEDISH_SWEDEN,              "sv_SE" },
175     { LANG_SWEDISH,        SUBLANG_SWEDISH_FINLAND,             "sv_FI" },
176     { LANG_THAI,           SUBLANG_NEUTRAL,                     "th" },
177     { LANG_THAI,           SUBLANG_THAI_THAILAND,               "th_TH" },
178     { LANG_TURKISH,        SUBLANG_NEUTRAL,                     "tr" },
179     { LANG_TURKISH,        SUBLANG_TURKISH_TURKEY,              "tr_TR" },
180     { LANG_URDU,           SUBLANG_NEUTRAL,                     "ur" },
181     { LANG_URDU,           SUBLANG_URDU_PAKISTAN,               "ur_PK" },
182     { LANG_INDONESIAN,     SUBLANG_NEUTRAL,                     "id" },
183     { LANG_INDONESIAN,     SUBLANG_INDONESIAN_INDONESIA,        "id_ID" },
184     { LANG_UKRAINIAN,      SUBLANG_NEUTRAL,                     "uk" },
185     { LANG_UKRAINIAN,      SUBLANG_UKRAINIAN_UKRAINE,           "uk_UA" },
186     { LANG_BELARUSIAN,     SUBLANG_NEUTRAL,                     "be" },
187     { LANG_BELARUSIAN,     SUBLANG_BELARUSIAN_BELARUS,          "be_BY" },
188     { LANG_SLOVENIAN,      SUBLANG_NEUTRAL,                     "sl" },
189     { LANG_SLOVENIAN,      SUBLANG_SLOVENIAN_SLOVENIA,          "sl_SI" },
190     { LANG_ESTONIAN,       SUBLANG_NEUTRAL,                     "et" },
191     { LANG_ESTONIAN,       SUBLANG_ESTONIAN_ESTONIA,            "et_EE" },
192     { LANG_LATVIAN,        SUBLANG_NEUTRAL,                     "lv" },
193     { LANG_LATVIAN,        SUBLANG_LATVIAN_LATVIA,              "lv_LV" },
194     { LANG_LITHUANIAN,     SUBLANG_NEUTRAL,                     "lt" },
195     { LANG_LITHUANIAN,     SUBLANG_LITHUANIAN_LITHUANIA,        "lt_LT" },
196     { LANG_PERSIAN,        SUBLANG_NEUTRAL,                     "fa" },
197     { LANG_PERSIAN,        SUBLANG_PERSIAN_IRAN,                "fa_IR" },
198     { LANG_ARMENIAN,       SUBLANG_NEUTRAL,                     "hy" },
199     { LANG_ARMENIAN,       SUBLANG_ARMENIAN_ARMENIA,            "hy_AM" },
200     { LANG_AZERI,          SUBLANG_NEUTRAL,                     "az" },
201     { LANG_AZERI,          SUBLANG_AZERI_LATIN,                 "az_AZ@latin" },
202     { LANG_AZERI,          SUBLANG_AZERI_CYRILLIC,              "az_AZ@cyrillic" },
203     { LANG_BASQUE,         SUBLANG_NEUTRAL,                     "eu" },
204     { LANG_BASQUE,         SUBLANG_BASQUE_BASQUE,               "eu_ES" },
205     { LANG_MACEDONIAN,     SUBLANG_NEUTRAL,                     "mk" },
206     { LANG_MACEDONIAN,     SUBLANG_MACEDONIAN_MACEDONIA,        "mk_MK" },
207     { LANG_AFRIKAANS,      SUBLANG_NEUTRAL,                     "af" },
208     { LANG_AFRIKAANS,      SUBLANG_AFRIKAANS_SOUTH_AFRICA,      "af_ZA" },
209     { LANG_GEORGIAN,       SUBLANG_NEUTRAL,                     "ka" },
210     { LANG_GEORGIAN,       SUBLANG_GEORGIAN_GEORGIA,            "ka_GE" },
211     { LANG_FAEROESE,       SUBLANG_NEUTRAL,                     "fo" },
212     { LANG_FAEROESE,       SUBLANG_FAEROESE_FAROE_ISLANDS,      "fo_FO" },
213     { LANG_HINDI,          SUBLANG_NEUTRAL,                     "hi" },
214     { LANG_HINDI,          SUBLANG_HINDI_INDIA,                 "hi_IN" },
215     { LANG_MALAY,          SUBLANG_NEUTRAL,                     "ms" },
216     { LANG_MALAY,          SUBLANG_MALAY_MALAYSIA,              "ms_MY" },
217     { LANG_MALAY,          SUBLANG_MALAY_BRUNEI_DARUSSALAM,     "ms_BN" },
218     { LANG_KAZAK,          SUBLANG_NEUTRAL,                     "kk" },
219     { LANG_KAZAK,          SUBLANG_KAZAK_KAZAKHSTAN,            "kk_KZ" },
220     { LANG_KYRGYZ,         SUBLANG_NEUTRAL,                     "ky" },
221     { LANG_KYRGYZ,         SUBLANG_KYRGYZ_KYRGYZSTAN,           "ky_KG" },
222     { LANG_SWAHILI,        SUBLANG_NEUTRAL,                     "sw" },
223     { LANG_SWAHILI,        SUBLANG_SWAHILI_KENYA,               "sw_KE" },
224     { LANG_UZBEK,          SUBLANG_NEUTRAL,                     "uz" },
225     { LANG_UZBEK,          SUBLANG_UZBEK_LATIN,                 "uz_UZ@latin" },
226     { LANG_UZBEK,          SUBLANG_UZBEK_CYRILLIC,              "uz_UZ@cyrillic" },
227     { LANG_TATAR,          SUBLANG_NEUTRAL,                     "tt" },
228     { LANG_TATAR,          SUBLANG_TATAR_RUSSIA,                "tt_TA" },
229     { LANG_PUNJABI,        SUBLANG_NEUTRAL,                     "pa" },
230     { LANG_PUNJABI,        SUBLANG_PUNJABI_INDIA,               "pa_IN" },
231     { LANG_GUJARATI,       SUBLANG_NEUTRAL,                     "gu" },
232     { LANG_GUJARATI,       SUBLANG_GUJARATI_INDIA,              "gu_IN" },
233     { LANG_ORIYA,          SUBLANG_NEUTRAL,                     "or" },
234     { LANG_ORIYA,          SUBLANG_ORIYA_INDIA,                 "or_IN" },
235     { LANG_TAMIL,          SUBLANG_NEUTRAL,                     "ta" },
236     { LANG_TAMIL,          SUBLANG_TAMIL_INDIA,                 "ta_IN" },
237     { LANG_TELUGU,         SUBLANG_NEUTRAL,                     "te" },
238     { LANG_TELUGU,         SUBLANG_TELUGU_INDIA,                "te_IN" },
239     { LANG_KANNADA,        SUBLANG_NEUTRAL,                     "kn" },
240     { LANG_KANNADA,        SUBLANG_KANNADA_INDIA,               "kn_IN" },
241     { LANG_MALAYALAM,      SUBLANG_NEUTRAL,                     "ml" },
242     { LANG_MALAYALAM,      SUBLANG_MALAYALAM_INDIA,             "ml_IN" },
243     { LANG_MARATHI,        SUBLANG_NEUTRAL,                     "mr" },
244     { LANG_MARATHI,        SUBLANG_MARATHI_INDIA,               "mr_IN" },
245     { LANG_SANSKRIT,       SUBLANG_NEUTRAL,                     "sa" },
246     { LANG_SANSKRIT,       SUBLANG_SANSKRIT_INDIA,              "sa_IN" },
247     { LANG_MONGOLIAN,      SUBLANG_NEUTRAL,                     "mn" },
248     { LANG_MONGOLIAN,      SUBLANG_MONGOLIAN_CYRILLIC_MONGOLIA, "mn_MN" },
249     { LANG_WELSH,          SUBLANG_NEUTRAL,                     "cy" },
250     { LANG_WELSH,          SUBLANG_WELSH_UNITED_KINGDOM,        "cy_GB" },
251     { LANG_GALICIAN,       SUBLANG_NEUTRAL,                     "gl" },
252     { LANG_GALICIAN,       SUBLANG_GALICIAN_GALICIAN,           "gl_ES" },
253     { LANG_KONKANI,        SUBLANG_NEUTRAL,                     "kok" },
254     { LANG_KONKANI,        SUBLANG_KONKANI_INDIA,               "kok_IN" },
255     { LANG_DIVEHI,         SUBLANG_NEUTRAL,                     "dv" },
256     { LANG_DIVEHI,         SUBLANG_DIVEHI_MALDIVES,             "dv_MV" },
257     { LANG_BRETON,         SUBLANG_NEUTRAL,                     "br" },
258     { LANG_BRETON,         SUBLANG_BRETON_FRANCE,               "br_FR" },
259
260 #ifdef LANG_ESPERANTO
261     { LANG_ESPERANTO,      SUBLANG_DEFAULT,                     "eo" },
262 #endif
263 #ifdef LANG_WALON
264     { LANG_WALON,          SUBLANG_NEUTRAL,                     "wa" },
265     { LANG_WALON,          SUBLANG_DEFAULT,                     "wa_BE" },
266 #endif
267 #ifdef LANG_CORNISH
268     { LANG_CORNISH,        SUBLANG_NEUTRAL,                     "kw" },
269     { LANG_CORNISH,        SUBLANG_DEFAULT,                     "kw_GB" },
270 #endif
271 #ifdef LANG_GAELIC
272     { LANG_GAELIC,         SUBLANG_NEUTRAL,                     "ga" },
273     { LANG_GAELIC,         SUBLANG_GAELIC,                      "ga_IE" },
274     { LANG_GAELIC,         SUBLANG_GAELIC_SCOTTISH,             "gd_GB" },
275     { LANG_GAELIC,         SUBLANG_GAELIC_MANX,                 "gv_GB" },
276 #endif
277 };
278
279 static void po_xerror( int severity, po_message_t message,
280                        const char *filename, size_t lineno, size_t column,
281                        int multiline_p, const char *message_text )
282 {
283     fprintf( stderr, "%s:%u:%u: %s\n",
284              filename, (unsigned int)lineno, (unsigned int)column, message_text );
285     if (severity) exit(1);
286 }
287
288 static void po_xerror2( int severity, po_message_t message1,
289                         const char *filename1, size_t lineno1, size_t column1,
290                         int multiline_p1, const char *message_text1,
291                         po_message_t message2,
292                         const char *filename2, size_t lineno2, size_t column2,
293                         int multiline_p2, const char *message_text2 )
294 {
295     fprintf( stderr, "%s:%u:%u: %s\n",
296              filename1, (unsigned int)lineno1, (unsigned int)column1, message_text1 );
297     fprintf( stderr, "%s:%u:%u: %s\n",
298              filename2, (unsigned int)lineno2, (unsigned int)column2, message_text2 );
299     if (severity) exit(1);
300 }
301
302 static const struct po_xerror_handler po_xerror_handler = { po_xerror, po_xerror2 };
303
304 static int is_english( const language_t *lan )
305 {
306     return lan->id == LANG_ENGLISH && lan->sub == SUBLANG_DEFAULT;
307 }
308
309 static char *convert_string_utf8( const string_t *str, int codepage )
310 {
311     string_t *newstr = convert_string( str, str_unicode, codepage );
312     char *buffer = xmalloc( newstr->size * 4 + 1 );
313     int len = wine_utf8_wcstombs( 0, newstr->str.wstr, newstr->size, buffer, newstr->size * 4 );
314     buffer[len] = 0;
315     free_string( newstr );
316     return buffer;
317 }
318
319 static char *convert_msgid_ascii( const string_t *str, int error_on_invalid_char )
320 {
321     int i;
322     string_t *newstr = convert_string( str, str_unicode, 1252 );
323     char *buffer = xmalloc( newstr->size + 1 );
324
325     for (i = 0; i < newstr->size; i++)
326     {
327         buffer[i] =  newstr->str.wstr[i];
328         if (newstr->str.wstr[i] >= 32 && newstr->str.wstr[i] <= 127) continue;
329         if (newstr->str.wstr[i] == '\t' || newstr->str.wstr[i] == '\n') continue;
330         if (error_on_invalid_char)
331         {
332             print_location( &newstr->loc );
333             error( "Invalid character %04x in source string\n", newstr->str.wstr[i] );
334         }
335         free_string( newstr );
336         return NULL;
337     }
338     buffer[i] = 0;
339     free_string( newstr );
340     return buffer;
341 }
342
343 static char *get_message_context( char **msgid )
344 {
345     static const char magic[] = "#msgctxt#";
346     char *id, *context;
347
348     if (strncmp( *msgid, magic, sizeof(magic) - 1 )) return NULL;
349     context = *msgid + sizeof(magic) - 1;
350     if (!(id = strchr( context, '#' ))) return NULL;
351     *id = 0;
352     *msgid = id + 1;
353     return context;
354 }
355
356 static po_message_t find_message( po_file_t po, const char *msgid, const char *msgctxt,
357                                   po_message_iterator_t *iterator )
358 {
359     po_message_t msg;
360     const char *context;
361
362     *iterator = po_message_iterator( po, NULL );
363     while ((msg = po_next_message( *iterator )))
364     {
365         if (strcmp( po_message_msgid( msg ), msgid )) continue;
366         if (!msgctxt) break;
367         if (!(context = po_message_msgctxt( msg ))) continue;
368         if (!strcmp( context, msgctxt )) break;
369     }
370     return msg;
371 }
372
373 static void add_po_string( po_file_t po, const string_t *msgid, const string_t *msgstr,
374                            const language_t *lang )
375 {
376     po_message_t msg;
377     po_message_iterator_t iterator;
378     int codepage;
379     char *id, *id_buffer, *context, *str = NULL, *str_buffer = NULL;
380
381     if (!msgid->size) return;
382
383     id_buffer = id = convert_msgid_ascii( msgid, 1 );
384     context = get_message_context( &id );
385
386     if (msgstr)
387     {
388         if (lang) codepage = get_language_codepage( lang->id, lang->sub );
389         else codepage = get_language_codepage( 0, 0 );
390         assert( codepage != -1 );
391         str_buffer = str = convert_string_utf8( msgstr, codepage );
392         if (is_english( lang )) get_message_context( &str );
393     }
394     if (!(msg = find_message( po, id, context, &iterator )))
395     {
396         msg = po_message_create();
397         po_message_set_msgid( msg, id );
398         po_message_set_msgstr( msg, str ? str : "" );
399         if (context) po_message_set_msgctxt( msg, context );
400         po_message_insert( iterator, msg );
401     }
402     if (msgid->loc.file) po_message_add_filepos( msg, msgid->loc.file, msgid->loc.line );
403     po_message_iterator_free( iterator );
404     free( id_buffer );
405     free( str_buffer );
406 }
407
408 struct po_file_lang
409 {
410     struct list entry;
411     language_t  lang;
412     po_file_t   po;
413 };
414
415 static struct list po_file_langs = LIST_INIT( po_file_langs );
416
417 static po_file_t create_po_file(void)
418 {
419     po_file_t po;
420     po_message_t msg;
421     po_message_iterator_t iterator;
422
423     po = po_file_create();
424     iterator = po_message_iterator( po, NULL );
425     msg = po_message_create();
426     po_message_set_msgid( msg, "" );
427     po_message_set_msgstr( msg,
428                            "Project-Id-Version: Wine\n"
429                            "Report-Msgid-Bugs-To: http://bugs.winehq.org\n"
430                            "POT-Creation-Date: N/A\n"
431                            "PO-Revision-Date: N/A\n"
432                            "Last-Translator: Automatically generated\n"
433                            "Language-Team: none\n"
434                            "MIME-Version: 1.0\n"
435                            "Content-Type: text/plain; charset=UTF-8\n"
436                            "Content-Transfer-Encoding: 8bit\n" );
437     po_message_insert( iterator, msg );
438     po_message_iterator_free( iterator );
439     return po;
440 }
441
442 static po_file_t get_po_file( const language_t *lang )
443 {
444     struct po_file_lang *po_file;
445
446     LIST_FOR_EACH_ENTRY( po_file, &po_file_langs, struct po_file_lang, entry )
447         if (po_file->lang.id == lang->id && po_file->lang.sub == lang->sub) return po_file->po;
448
449     /* create a new one */
450     po_file = xmalloc( sizeof(*po_file) );
451     po_file->lang = *lang;
452     po_file->po = create_po_file();
453     list_add_tail( &po_file_langs, &po_file->entry );
454     return po_file->po;
455 }
456
457 static char *get_po_file_name( const language_t *lang )
458 {
459     unsigned int i;
460     char name[40];
461
462     sprintf( name, "%02x-%02x", lang->id, lang->sub );
463     for (i = 0; i < sizeof(languages)/sizeof(languages[0]); i++)
464     {
465         if (languages[i].id == lang->id && languages[i].sub == lang->sub)
466         {
467             strcpy( name, languages[i].name );
468             break;
469         }
470     }
471     strcat( name, ".po" );
472     return xstrdup( name );
473 }
474
475 static unsigned int flush_po_files( const char *output_name )
476 {
477     struct po_file_lang *po_file, *next;
478     unsigned int count = 0;
479
480     LIST_FOR_EACH_ENTRY_SAFE( po_file, next, &po_file_langs, struct po_file_lang, entry )
481     {
482         char *name = get_po_file_name( &po_file->lang );
483         if (output_name)
484         {
485             const char *p = strrchr( output_name, '/' );
486             if (p) p++;
487             else p = output_name;
488             if (!strcmp( p, name ))
489             {
490                 po_file_write( po_file->po, name, &po_xerror_handler );
491                 count++;
492             }
493         }
494         else  /* no specified output name, output a file for every language found */
495         {
496             po_file_write( po_file->po, name, &po_xerror_handler );
497             count++;
498             fprintf( stderr, "created %s\n", name );
499         }
500         po_file_free( po_file->po );
501         list_remove( &po_file->entry );
502         free( po_file );
503         free( name );
504     }
505     return count;
506 }
507
508 static int control_has_title( const control_t *ctrl )
509 {
510     if (!ctrl->title) return 0;
511     if (ctrl->title->type != name_str) return 0;
512     /* check for text static control */
513     if (ctrl->ctlclass && ctrl->ctlclass->type == name_ord && ctrl->ctlclass->name.i_name == CT_STATIC)
514     {
515         switch (ctrl->style->or_mask & SS_TYPEMASK)
516         {
517         case SS_LEFT:
518         case SS_CENTER:
519         case SS_RIGHT:
520             return 1;
521         default:
522             return 0;
523         }
524     }
525     return 1;
526 }
527
528 static void add_pot_stringtable( po_file_t po, const resource_t *res )
529 {
530     const stringtable_t *stt = res->res.stt;
531     int i;
532
533     while (stt)
534     {
535         for (i = 0; i < stt->nentries; i++)
536             if (stt->entries[i].str) add_po_string( po, stt->entries[i].str, NULL, NULL );
537         stt = stt->next;
538     }
539 }
540
541 static void add_po_stringtable( const resource_t *english, const resource_t *res )
542 {
543     const stringtable_t *english_stt = english->res.stt;
544     const stringtable_t *stt = res->res.stt;
545     po_file_t po = get_po_file( stt->lvc.language );
546     int i;
547
548     while (english_stt && stt)
549     {
550         for (i = 0; i < stt->nentries; i++)
551             if (english_stt->entries[i].str && stt->entries[i].str)
552                 add_po_string( po, english_stt->entries[i].str, stt->entries[i].str, stt->lvc.language );
553         stt = stt->next;
554         english_stt = english_stt->next;
555     }
556 }
557
558 static void add_pot_dialog_controls( po_file_t po, const control_t *ctrl )
559 {
560     while (ctrl)
561     {
562         if (control_has_title( ctrl )) add_po_string( po, ctrl->title->name.s_name, NULL, NULL );
563         ctrl = ctrl->next;
564     }
565 }
566
567 static void add_pot_dialog( po_file_t po, const resource_t *res )
568 {
569     const dialog_t *dlg = res->res.dlg;
570
571     if (dlg->title) add_po_string( po, dlg->title, NULL, NULL );
572     if (dlg->font) add_po_string( po, dlg->font->name, NULL, NULL );
573     add_pot_dialog_controls( po, dlg->controls );
574 }
575
576 static void add_po_dialog_controls( po_file_t po, const control_t *english_ctrl,
577                                     const control_t *ctrl, const language_t *lang )
578 {
579     while (english_ctrl && ctrl)
580     {
581         if (control_has_title( english_ctrl ) && control_has_title( ctrl ))
582             add_po_string( po, english_ctrl->title->name.s_name, ctrl->title->name.s_name, lang );
583
584         ctrl = ctrl->next;
585         english_ctrl = english_ctrl->next;
586     }
587 }
588
589 static void add_po_dialog( const resource_t *english, const resource_t *res )
590 {
591     const dialog_t *english_dlg = english->res.dlg;
592     const dialog_t *dlg = res->res.dlg;
593     po_file_t po = get_po_file( dlg->lvc.language );
594
595     if (english_dlg->title && dlg->title)
596         add_po_string( po, english_dlg->title, dlg->title, dlg->lvc.language );
597     if (english_dlg->font && dlg->font)
598         add_po_string( po, english_dlg->font->name, dlg->font->name, dlg->lvc.language );
599     add_po_dialog_controls( po, english_dlg->controls, dlg->controls, dlg->lvc.language );
600 }
601
602 static void add_pot_menu_items( po_file_t po, const menu_item_t *item )
603 {
604     while (item)
605     {
606         if (item->name) add_po_string( po, item->name, NULL, NULL );
607         if (item->popup) add_pot_menu_items( po, item->popup );
608         item = item->next;
609     }
610 }
611
612 static void add_pot_menu( po_file_t po, const resource_t *res )
613 {
614     add_pot_menu_items( po, res->res.men->items );
615 }
616
617 static void add_po_menu_items( po_file_t po, const menu_item_t *english_item,
618                                const menu_item_t *item, const language_t *lang )
619 {
620     while (english_item && item)
621     {
622         if (english_item->name && item->name)
623             add_po_string( po, english_item->name, item->name, lang );
624         if (english_item->popup && item->popup)
625             add_po_menu_items( po, english_item->popup, item->popup, lang );
626         item = item->next;
627         english_item = english_item->next;
628     }
629 }
630
631 static void add_po_menu( const resource_t *english, const resource_t *res )
632 {
633     const menu_item_t *english_items = english->res.men->items;
634     const menu_item_t *items = res->res.men->items;
635     po_file_t po = get_po_file( res->res.men->lvc.language );
636
637     add_po_menu_items( po, english_items, items, res->res.men->lvc.language );
638 }
639
640 static resource_t *find_english_resource( resource_t *res )
641 {
642     resource_t *ptr;
643
644     for (ptr = resource_top; ptr; ptr = ptr->next)
645     {
646         if (ptr->type != res->type) continue;
647         if (!ptr->lan) continue;
648         if (!is_english( ptr->lan )) continue;
649         if (compare_name_id( ptr->name, res->name )) continue;
650         return ptr;
651     }
652     return NULL;
653 }
654
655 void write_pot_file( const char *outname )
656 {
657     resource_t *res;
658     po_file_t po = create_po_file();
659
660     for (res = resource_top; res; res = res->next)
661     {
662         if (!is_english( res->lan )) continue;
663
664         switch (res->type)
665         {
666         case res_acc: break;  /* FIXME */
667         case res_dlg: add_pot_dialog( po, res ); break;
668         case res_men: add_pot_menu( po, res ); break;
669         case res_stt: add_pot_stringtable( po, res ); break;
670         case res_msg: break;  /* FIXME */
671         default: break;
672         }
673     }
674     po_file_write( po, outname, &po_xerror_handler );
675     po_file_free( po );
676 }
677
678 void write_po_files( const char *outname )
679 {
680     resource_t *res, *english;
681     po_file_t po;
682
683     for (res = resource_top; res; res = res->next)
684     {
685         if (!(english = find_english_resource( res ))) continue;
686         po = get_po_file( res->lan );
687
688         switch (res->type)
689         {
690         case res_acc: break;  /* FIXME */
691         case res_dlg: add_po_dialog( english, res ); break;
692         case res_men: add_po_menu( english, res ); break;
693         case res_stt: add_po_stringtable( english, res ); break;
694         case res_msg: break;  /* FIXME */
695         default: break;
696         }
697     }
698     if (!flush_po_files( outname ))
699     {
700         if (outname) error( "No translations found for %s\n", outname );
701         else error( "No translations found\n" );
702     }
703 }
704
705 static resource_t *new_top, *new_tail;
706
707 static version_t *get_dup_version( language_t *lang )
708 {
709     /* English "translations" take precedence over the original rc contents */
710     return new_version( is_english( lang ) ? 1 : -1 );
711 }
712
713 static name_id_t *dup_name_id( name_id_t *id )
714 {
715     name_id_t *new;
716
717     if (!id || id->type != name_str) return id;
718     new = new_name_id();
719     *new = *id;
720     new->name.s_name = convert_string( id->name.s_name, str_unicode, 1252 );
721     return new;
722 }
723
724 static resource_t *dup_resource( resource_t *res, language_t *lang )
725 {
726     resource_t *new = xmalloc( sizeof(*new) );
727
728     *new = *res;
729     new->lan = lang;
730     new->next = new->prev = NULL;
731     new->name = dup_name_id( res->name );
732
733     switch (res->type)
734     {
735     case res_dlg:
736         new->res.dlg = xmalloc( sizeof(*(new)->res.dlg) );
737         *new->res.dlg = *res->res.dlg;
738         new->res.dlg->lvc.language = lang;
739         new->res.dlg->lvc.version = get_dup_version( lang );
740         break;
741     case res_men:
742         new->res.men = xmalloc( sizeof(*(new)->res.men) );
743         *new->res.men = *res->res.men;
744         new->res.men->lvc.language = lang;
745         new->res.men->lvc.version = get_dup_version( lang );
746         break;
747     case res_stt:
748         new->res.stt = xmalloc( sizeof(*(new)->res.stt) );
749         *new->res.stt = *res->res.stt;
750         new->res.stt->lvc.language = lang;
751         new->res.stt->lvc.version = get_dup_version( lang );
752         break;
753     default:
754         assert(0);
755     }
756     return new;
757 }
758
759 static string_t *translate_string( po_file_t po, string_t *str, int *found )
760 {
761     po_message_t msg;
762     po_message_iterator_t iterator;
763     string_t *new;
764     const char *transl;
765     int res;
766     char *buffer, *msgid, *context;
767
768     if (!str->size || !(buffer = convert_msgid_ascii( str, 0 )))
769         return convert_string( str, str_unicode, 1252 );
770
771     msgid = buffer;
772     context = get_message_context( &msgid );
773     msg = find_message( po, msgid, context, &iterator );
774     po_message_iterator_free( iterator );
775
776     if (msg && !po_message_is_fuzzy( msg ))
777     {
778         transl = po_message_msgstr( msg );
779         if (!transl[0]) transl = msgid;  /* ignore empty strings */
780         else (*found)++;
781     }
782     else transl = msgid;
783
784     new = xmalloc( sizeof(*new) );
785     new->type = str_unicode;
786     new->size = wine_utf8_mbstowcs( 0, transl, strlen(transl), NULL, 0 );
787     new->str.wstr = xmalloc( (new->size+1) * sizeof(WCHAR) );
788     res = wine_utf8_mbstowcs( MB_ERR_INVALID_CHARS, transl, strlen(transl), new->str.wstr, new->size );
789     if (res == -2)
790         error( "Invalid utf-8 character in string '%s'\n", transl );
791     new->str.wstr[new->size] = 0;
792     free( buffer );
793     return new;
794 }
795
796 static control_t *translate_controls( po_file_t po, control_t *ctrl, int *found )
797 {
798     control_t *new, *head = NULL, *tail = NULL;
799
800     while (ctrl)
801     {
802         new = xmalloc( sizeof(*new) );
803         *new = *ctrl;
804         if (control_has_title( ctrl ))
805         {
806             new->title = new_name_id();
807             *new->title = *ctrl->title;
808             new->title->name.s_name = translate_string( po, ctrl->title->name.s_name, found );
809         }
810         else new->title = dup_name_id( ctrl->title );
811         new->ctlclass = dup_name_id( ctrl->ctlclass );
812         if (tail) tail->next = new;
813         else head = new;
814         new->next = NULL;
815         new->prev = tail;
816         tail = new;
817         ctrl = ctrl->next;
818     }
819     return head;
820 }
821
822 static menu_item_t *translate_items( po_file_t po, menu_item_t *item, int *found )
823 {
824     menu_item_t *new, *head = NULL, *tail = NULL;
825
826     while (item)
827     {
828         new = xmalloc( sizeof(*new) );
829         *new = *item;
830         if (item->name) new->name = translate_string( po, item->name, found );
831         if (item->popup) new->popup = translate_items( po, item->popup, found );
832         if (tail) tail->next = new;
833         else head = new;
834         new->next = NULL;
835         new->prev = tail;
836         tail = new;
837         item = item->next;
838     }
839     return head;
840 }
841
842 static stringtable_t *translate_stringtable( po_file_t po, stringtable_t *stt,
843                                              language_t *lang, int *found )
844 {
845     stringtable_t *new, *head = NULL, *tail = NULL;
846     int i;
847
848     while (stt)
849     {
850         new = xmalloc( sizeof(*new) );
851         *new = *stt;
852         new->lvc.language = lang;
853         new->lvc.version = get_dup_version( lang );
854         new->entries = xmalloc( new->nentries * sizeof(*new->entries) );
855         memcpy( new->entries, stt->entries, new->nentries * sizeof(*new->entries) );
856         for (i = 0; i < stt->nentries; i++)
857             if (stt->entries[i].str)
858                 new->entries[i].str = translate_string( po, stt->entries[i].str, found );
859
860         if (tail) tail->next = new;
861         else head = new;
862         new->next = NULL;
863         new->prev = tail;
864         tail = new;
865         stt = stt->next;
866     }
867     return head;
868 }
869
870 static void translate_dialog( po_file_t po, dialog_t *dlg, dialog_t *new, int *found )
871 {
872     if (dlg->title) new->title = translate_string( po, dlg->title, found );
873     if (dlg->font)
874     {
875         new->font = xmalloc( sizeof(*dlg->font) );
876         new->font = dlg->font;
877         new->font->name = translate_string( po, dlg->font->name, found );
878     }
879     new->controls = translate_controls( po, dlg->controls, found );
880 }
881
882 static void translate_resources( po_file_t po, language_t *lang )
883 {
884     resource_t *res;
885
886     for (res = resource_top; res; res = res->next)
887     {
888         resource_t *new = NULL;
889         int found = 0;
890
891         if (!is_english( res->lan )) continue;
892
893         switch (res->type)
894         {
895         case res_acc:
896             /* FIXME */
897             break;
898         case res_dlg:
899             new = dup_resource( res, lang );
900             translate_dialog( po, res->res.dlg, new->res.dlg, &found );
901             break;
902         case res_men:
903             new = dup_resource( res, lang );
904             new->res.men->items = translate_items( po, res->res.men->items, &found );
905             break;
906         case res_stt:
907             new = dup_resource( res, lang );
908             new->res.stt = translate_stringtable( po, res->res.stt, lang, &found );
909             break;
910         case res_msg:
911             /* FIXME */
912             break;
913         default:
914             break;
915         }
916
917         if (new && found)
918         {
919             if (new_tail) new_tail->next = new;
920             else new_top = new;
921             new->prev = new_tail;
922             new_tail = new;
923         }
924     }
925 }
926
927 void add_translations( const char *po_dir )
928 {
929     resource_t *res;
930     po_file_t po;
931     char buffer[256];
932     char *p, *tok, *name;
933     unsigned int i;
934     FILE *f;
935
936     /* first check if we have English resources to translate */
937     for (res = resource_top; res; res = res->next) if (is_english( res->lan )) break;
938     if (!res) return;
939
940     new_top = new_tail = NULL;
941
942     name = strmake( "%s/LINGUAS", po_dir );
943     if (!(f = fopen( name, "r" ))) return;
944     free( name );
945     while (fgets( buffer, sizeof(buffer), f ))
946     {
947         if ((p = strchr( buffer, '#' ))) *p = 0;
948         for (tok = strtok( buffer, " \t\r\n" ); tok; tok = strtok( NULL, " \t\r\n" ))
949         {
950             for (i = 0; i < sizeof(languages)/sizeof(languages[0]); i++)
951                 if (!strcmp( tok, languages[i].name )) break;
952
953             if (i == sizeof(languages)/sizeof(languages[0]))
954                 error( "unknown language '%s'\n", tok );
955
956             name = strmake( "%s/%s.po", po_dir, tok );
957             if (!(po = po_file_read( name, &po_xerror_handler )))
958                 error( "cannot load po file for language '%s'\n", tok );
959             translate_resources( po, new_language(languages[i].id, languages[i].sub) );
960             po_file_free( po );
961             free( name );
962         }
963     }
964     fclose( f );
965
966     /* prepend the translated resources to the global list */
967     if (new_tail)
968     {
969         new_tail->next = resource_top;
970         resource_top->prev = new_tail;
971         resource_top = new_top;
972     }
973 }
974
975 #else  /* HAVE_LIBGETTEXTPO */
976
977 void write_pot_file( const char *outname )
978 {
979     error( "PO files not supported in this wrc build\n" );
980 }
981
982 void write_po_files( const char *outname )
983 {
984     error( "PO files not supported in this wrc build\n" );
985 }
986
987 void add_translations( const char *po_dir )
988 {
989 }
990
991 #endif