1 /*
2 Checks the po file for C format correctness.
3 To compile it you need the gettext package installed
4 with its development files.
5 Compilation commandline:
6
7 gcc -lgettextpo -o msgcheckformat msgcheckformat.c
8
9 Usage:
10
11 msgcheckformat <pofile> [ask|fix]
12
13 ----------------------------------------------------------------------------
14
15 Copyright (C) 2004 Szymon Stefanek (pragma at kvirc dot net),
16 2010 Fabio Bas (ctrlaltca at gmail dot com)
17
18 This program is free software; you can redistribute it and/or modify
19 it under the terms of the GNU General Public License as published by
20 the Free Software Foundation; either version 2, or (at your option)
21 any later version.
22
23 This program is distributed in the hope that it will be useful,
24 but WITHOUT ANY WARRANTY; without even the implied warranty of
25 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 GNU General Public License for more details.
27 */
28
29
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <unistd.h>
36 #include <gettext-po.h>
37
38 int total_errors = 0;
39 int total_warnings = 0;
40 int total_remove = 0;
41 int total_ignore = 0;
42 int total_fix = 0;
43 int solution_mode = 0;
44
po_xerror(int severity,po_message_t message,const char * filename,size_t lineno,size_t column,int multiline_p,const char * message_text)45 static void po_xerror( int severity, po_message_t message,
46 const char *filename, size_t lineno, size_t column,
47 int multiline_p, const char *message_text )
48 {
49 fprintf( stderr, "%s:%u:%u: %s\n",
50 filename, (unsigned int)lineno, (unsigned int)column, message_text );
51 if (severity) exit(1);
52 }
53
po_xerror2(int severity,po_message_t message1,const char * filename1,size_t lineno1,size_t column1,int multiline_p1,const char * message_text1,po_message_t message2,const char * filename2,size_t lineno2,size_t column2,int multiline_p2,const char * message_text2)54 static void po_xerror2( int severity, po_message_t message1,
55 const char *filename1, size_t lineno1, size_t column1,
56 int multiline_p1, const char *message_text1,
57 po_message_t message2,
58 const char *filename2, size_t lineno2, size_t column2,
59 int multiline_p2, const char *message_text2 )
60 {
61 fprintf( stderr, "%s:%u:%u: %s\n",
62 filename1, (unsigned int)lineno1, (unsigned int)column1, message_text1 );
63 fprintf( stderr, "%s:%u:%u: %s\n",
64 filename2, (unsigned int)lineno2, (unsigned int)column2, message_text2 );
65 if (severity) exit(1);
66 }
67
68 static const struct po_xerror_handler po_xerror_handler = { po_xerror, po_xerror2 };
69
replace_crlf(const char * msg,char * buf)70 static void replace_crlf(const char * msg,char * buf)
71 {
72 while(*msg)
73 {
74 if(*msg == '\r')
75 {
76 *buf++ = '\\';
77 *buf++ = 'r';
78 msg++;
79 } else if(*msg == '\n')
80 {
81 *buf++ = '\\';
82 *buf++ = 'n';
83 msg++;
84 } else *buf++ = *msg++;
85 }
86 }
87
88
print_error(po_message_t m,const char * msgid,const char * msgstr,const char * error,int solution)89 static int print_error(po_message_t m,const char * msgid,const char * msgstr,const char * error, int solution)
90 {
91 char bufferid[16000];
92 char bufferstr[16000];
93 int s = 0;
94 int is_fuzzy = po_message_is_fuzzy(m) ? 1 : 0;
95 replace_crlf(msgid,bufferid);
96 replace_crlf(msgstr,bufferstr);
97 fprintf(stderr,"%s in %s:\n msgid: %s\n msgstr: %s\n reason: %s\n\n",
98 is_fuzzy ? "Warning" : "Error",
99 is_fuzzy ? "fuzzy entry" : "entry",
100 bufferid,bufferstr,error);
101 if(is_fuzzy)total_warnings++;
102 else total_errors++;
103 switch(solution_mode)
104 {
105 case 1:
106 // ask the user
107 switch(solution)
108 {
109 case 1:
110 printf("Pick a solution: (i)gnore the problem, or (r)emove the offending translation:");
111 while(s != 'i' && s != 'r') s = getchar();
112 if(s == 'r')
113 {
114 // delete the wrong translation
115 /* Change the msgstr (translation) of a message.
116 Use an empty string to denote an untranslated message. */
117 po_message_set_msgstr(m, "");
118 total_remove++;
119 } else {
120 total_ignore++;
121 }
122 break;
123 case 2:
124 printf("Pick a solution: (i)gnore the problem, (f)ix it or (r)emove the offending translation:");
125 while(s != 'i' && s != 'f' && s != 'r') s = getchar();
126 if(s == 'r')
127 {
128 // delete the wrong translation
129 /* Change the msgstr (translation) of a message.
130 Use an empty string to denote an untranslated message. */
131 po_message_set_msgstr(m, "");
132 total_remove++;
133 } else if(s == 'f') {
134 total_fix++;
135 return 1;
136 } else {
137 total_ignore++;
138 }
139 break;
140 }
141 break;
142 case 2:
143 // fix automatically
144 switch(solution)
145 {
146 case 1:
147 // delete the wrong translation
148 /* Change the msgstr (translation) of a message.
149 Use an empty string to denote an untranslated message. */
150 po_message_set_msgstr(m, "");
151 total_remove++;
152 break;
153 case 2:
154 total_fix++;
155 return 1;
156 break;
157 }
158 break;
159 default:
160 // do nothing
161 break;
162 }
163 return 0;
164 }
165
is_valid_fmtstr(const char * p)166 inline int is_valid_fmtstr(const char * p)
167 {
168 if(!*p) return 0;
169 if(*p != '%') return 0;
170 p++;
171 if(!*p) return 0;
172 if((*p >= '1' && *p <= '9') ||
173 (*p >= 'a' && *p <= 'z') ||
174 (*p >= 'A' && *p <= 'Z'))
175 {
176 p--;
177 return 1;
178 }
179 p--;
180 return 0;
181 }
182
process_message(po_message_t m)183 static void process_message(po_message_t m)
184 {
185 const char * msgstr = po_message_msgstr(m);
186 const char * msgid = po_message_msgid(m);
187 const char *p;
188 char *q;
189 char *fixedstr;
190
191 if(msgid[0] == '\0')return;
192 if(strlen(msgid) < 1)return;
193 if(strlen(msgstr) < 1)return;
194 if(msgstr[0] == '\0')return;
195
196 fixedstr = strdup(msgstr);
197
198 p = msgid;
199 q = fixedstr;
200
201 while(*p && *q)
202 {
203 while(*p && !is_valid_fmtstr(p))p++;
204 while(*q && !is_valid_fmtstr(q))q++;
205 if(*q && *p)
206 {
207 q++;
208 p++;
209 if(*p != *q)
210 {
211 char buff[200];
212 sprintf(buff,"Mismatched format character (%c != %c)",*p,*q);
213 if(print_error(m,msgid,fixedstr,buff, 2))
214 {
215 //fix the format string
216 *q=*p;
217 po_message_set_msgstr(m, fixedstr);
218 }
219 }
220 }
221 }
222
223 if(*p || *q)
224 {
225 print_error(m,msgid,msgstr,"Mismatched number of format characters", 1);
226 }
227 }
228
process_messages(po_message_iterator_t it)229 static void process_messages(po_message_iterator_t it)
230 {
231 po_message_t m = po_next_message(it);
232 while(m)
233 {
234 process_message(m);
235 m = po_next_message(it);
236 }
237 }
238
main(int argc,char ** argv)239 int main(int argc, char **argv)
240 {
241 po_file_t po;
242 po_message_iterator_t it;
243 const char * const * domains;
244
245 if(!argv[1])
246 {
247 fprintf(stderr,"usage: %s <pofile> [ask|fix]\n",argv[0]);
248 return 0;
249 }
250
251 if(argv[2])
252 {
253 if(!strncmp(argv[2], "ask", 3))
254 {
255 solution_mode=1;
256 } else if(!strncmp(argv[2], "fix", 3)) {
257 solution_mode=2;
258 } else {
259 fprintf(stderr,"usage: %s <pofile> [ask|fix]\n",argv[0]);
260 return 0;
261 }
262 }
263
264 po = po_file_read(argv[1], &po_xerror_handler);
265
266 if(!po)
267 {
268 fprintf(stderr,"Couldn't read the input po file\n");
269 return 0;
270 }
271
272 domains = po_file_domains(po);
273
274 if(!domains)
275 {
276 fprintf(stderr,"Couldn't find the message domains in the po file\n");
277 return 0;
278 }
279
280 while(*domains)
281 {
282 it = po_message_iterator(po,*domains);
283 process_messages(it);
284 po_message_iterator_free(it);
285 domains++;
286 }
287
288 if(total_errors == 0 && total_warnings == 0)
289 {
290 fprintf(stderr,"No errors found\n");
291 } else if(solution_mode==0) {
292 fprintf(stderr,"%d warnings, %d errors\n",total_warnings,total_errors);
293 } else {
294 fprintf(stderr,"%d warnings, %d errors, %d fixes, %d removed, %d ignored\n",total_warnings,total_errors, total_fix, total_remove, total_ignore);
295 if(total_fix || total_remove)
296 {
297 int s = 0;
298 printf("Commit changes to file? (y)es, (n)o:");
299 while(s != 'y' && s != 'n') s = getchar();
300 if(s == 'y')
301 {
302 if(po_file_write(po, argv[1], &po_xerror_handler)) printf("Changes committed.");
303 else printf("Error writing file.");
304 } else {
305 printf("Changes discarded.");
306 }
307 }
308 }
309
310 po_file_free(po);
311
312 return 0;
313 }
314