]>
Commit | Line | Data |
---|---|---|
ef416fc2 | 1 | /* |
f2d18633 | 2 | * "$Id$" |
ef416fc2 | 3 | * |
71e16022 | 4 | * HTTP-based translation program for CUPS. |
ef416fc2 | 5 | * |
6 | * This program uses Google to translate the CUPS template (cups.pot) to | |
7 | * several different languages. The translation isn't perfect, but it's | |
8 | * a start (better than working from scratch.) | |
9 | * | |
71e16022 | 10 | * Copyright 2007-2010 by Apple Inc. |
ef416fc2 | 11 | * Copyright 1997-2006 by Easy Software Products. |
12 | * | |
13 | * These coded instructions, statements, and computer programs are the | |
bc44d920 | 14 | * property of Apple Inc. and are protected by Federal copyright |
15 | * law. Distribution and use rights are outlined in the file "LICENSE.txt" | |
16 | * which should have been included with this file. If this file is | |
17 | * file is missing or damaged, see the license at "http://www.cups.org/". | |
ef416fc2 | 18 | * |
19 | * Contents: | |
20 | * | |
e1d6a774 | 21 | * main() - Main entry. |
22 | * save_messages() - Save messages to a .po file. | |
23 | * translate_messages() - Translate messages using Google. | |
24 | * write_string() - Write a quoted string to a file. | |
ef416fc2 | 25 | */ |
26 | ||
27 | /* | |
28 | * Include necessary headers... | |
29 | */ | |
30 | ||
71e16022 | 31 | #include <cups/cups-private.h> |
ef416fc2 | 32 | #include <unistd.h> |
33 | ||
34 | ||
35 | /* | |
36 | * Local functions... | |
37 | */ | |
38 | ||
39 | int save_messages(cups_array_t *cat, const char *filename); | |
40 | int translate_messages(cups_array_t *cat, const char *lang); | |
41 | int write_string(cups_file_t *fp, const char *s); | |
42 | ||
43 | ||
44 | /* | |
45 | * 'main()' - Main entry. | |
46 | */ | |
47 | ||
48 | int /* O - Exit status */ | |
49 | main(int argc, /* I - Number of command-line arguments */ | |
50 | char *argv[]) /* I - Command-line arguments */ | |
51 | { | |
52 | cups_array_t *cat; /* Message catalog */ | |
53 | ||
54 | ||
55 | if (argc != 3) | |
56 | { | |
57 | fputs("Usage: translate cups_language.po language\n", stderr); | |
58 | return (1); | |
59 | } | |
60 | ||
61 | if (access(argv[1], 0)) | |
8b116e60 | 62 | cat = _cupsMessageLoad("cups.pot", 1); |
ef416fc2 | 63 | else |
8b116e60 | 64 | cat = _cupsMessageLoad(argv[1], 1); |
ef416fc2 | 65 | |
66 | if (!cat) | |
67 | { | |
68 | puts("Unable to load message catalog."); | |
69 | return (1); | |
70 | } | |
71 | ||
72 | if (!translate_messages(cat, argv[2])) | |
73 | { | |
74 | puts("Unable to translate message catalog."); | |
75 | return (1); | |
76 | } | |
77 | ||
78 | if (!save_messages(cat, argv[1])) | |
79 | { | |
80 | puts("Unable to save message catalog."); | |
81 | return (1); | |
82 | } | |
83 | ||
84 | return (0); | |
85 | } | |
86 | ||
87 | ||
88 | /* | |
89 | * 'save_messages()' - Save messages to a .po file. | |
90 | */ | |
91 | ||
92 | int /* O - 1 on success, 0 on error */ | |
93 | save_messages(cups_array_t *cat, /* I - Message catalog */ | |
94 | const char *filename) /* I - File to save to */ | |
95 | { | |
96 | _cups_message_t *m; /* Current message */ | |
97 | cups_file_t *fp; /* File pointer */ | |
98 | ||
99 | ||
100 | /* | |
101 | * Open the message catalog... | |
102 | */ | |
103 | ||
104 | if ((fp = cupsFileOpen(filename, "w")) == NULL) | |
105 | return (0); | |
106 | ||
107 | /* | |
108 | * Save the messages to a file... | |
109 | */ | |
110 | ||
111 | for (m = (_cups_message_t *)cupsArrayFirst(cat); | |
112 | m; | |
113 | m = (_cups_message_t *)cupsArrayNext(cat)) | |
114 | { | |
115 | if (cupsFilePuts(fp, "msgid \"") < 0) | |
116 | break; | |
117 | ||
118 | if (!write_string(fp, m->id)) | |
119 | break; | |
120 | ||
121 | if (cupsFilePuts(fp, "\"\nmsgstr \"") < 0) | |
122 | break; | |
123 | ||
124 | if (m->str) | |
125 | { | |
126 | if (!write_string(fp, m->str)) | |
127 | break; | |
128 | } | |
129 | ||
130 | if (cupsFilePuts(fp, "\"\n") < 0) | |
131 | break; | |
132 | } | |
133 | ||
134 | cupsFileClose(fp); | |
135 | ||
136 | return (!m); | |
137 | } | |
138 | ||
139 | ||
140 | /* | |
141 | * 'translate_messages()' - Translate messages using Google. | |
142 | */ | |
143 | ||
144 | int /* O - 1 on success, 0 on error */ | |
145 | translate_messages(cups_array_t *cat, /* I - Message catalog */ | |
146 | const char *lang) /* I - Output language... */ | |
147 | { | |
148 | /* | |
149 | * Google provides a simple translation/language tool for translating | |
150 | * from one language to another. It is far from perfect, however it | |
151 | * can be used to get a basic translation done or update an existing | |
152 | * translation when no other resources are available. | |
153 | * | |
154 | * Translation requests are sent as HTTP POSTs to | |
155 | * "http://translate.google.com/translate_t" with the following form | |
156 | * variables: | |
157 | * | |
158 | * Name Description Value | |
159 | * -------- ---------------------------------- ---------------- | |
160 | * hl Help language? "en" | |
161 | * ie Input encoding "UTF8" | |
162 | * langpair Language pair "en|" + language | |
163 | * oe Output encoding "UTF8" | |
164 | * text Text to translate translation string | |
165 | */ | |
166 | ||
167 | int ret; /* Return value */ | |
168 | _cups_message_t *m; /* Current message */ | |
169 | int tries; /* Number of tries... */ | |
170 | http_t *http; /* HTTP connection */ | |
171 | http_status_t status; /* Status of POST request */ | |
172 | char *idptr, /* Pointer into msgid */ | |
173 | buffer[65536], /* Input/output buffer */ | |
174 | *bufptr, /* Pointer into buffer */ | |
175 | *bufend, /* Pointer to end of buffer */ | |
176 | length[16]; /* Content length */ | |
177 | int bytes; /* Number of bytes read */ | |
178 | ||
179 | ||
180 | /* | |
181 | * Connect to translate.google.com... | |
182 | */ | |
183 | ||
184 | puts("Connecting to translate.google.com..."); | |
185 | ||
186 | if ((http = httpConnect("translate.google.com", 80)) == NULL) | |
187 | { | |
188 | perror("Unable to connect to translate.google.com"); | |
189 | return (0); | |
190 | } | |
191 | ||
192 | /* | |
193 | * Scan the current messages, requesting a translation of any untranslated | |
194 | * messages... | |
195 | */ | |
196 | ||
197 | for (m = (_cups_message_t *)cupsArrayFirst(cat), ret = 1; | |
198 | m; | |
199 | m = (_cups_message_t *)cupsArrayNext(cat)) | |
200 | { | |
201 | /* | |
202 | * Skip messages that are already translated... | |
203 | */ | |
204 | ||
205 | if (m->str && m->str[0]) | |
206 | continue; | |
207 | ||
208 | /* | |
209 | * Encode the form data into the buffer... | |
210 | */ | |
211 | ||
212 | snprintf(buffer, sizeof(buffer), | |
213 | "hl=en&ie=UTF8&langpair=en|%s&oe=UTF8&text=", lang); | |
214 | bufptr = buffer + strlen(buffer); | |
215 | bufend = buffer + sizeof(buffer) - 5; | |
216 | ||
217 | for (idptr = m->id; *idptr && bufptr < bufend; idptr ++) | |
218 | if (*idptr == ' ') | |
219 | *bufptr++ = '+'; | |
220 | else if (*idptr < ' ' || *idptr == '%') | |
221 | { | |
222 | sprintf(bufptr, "%%%02X", *idptr & 255); | |
223 | bufptr += 3; | |
224 | } | |
225 | else if (*idptr != '&') | |
226 | *bufptr++ = *idptr; | |
227 | ||
228 | *bufptr++ = '&'; | |
229 | *bufptr = '\0'; | |
230 | ||
e1d6a774 | 231 | sprintf(length, "%d", (int)(bufptr - buffer)); |
ef416fc2 | 232 | |
233 | /* | |
234 | * Send the request... | |
235 | */ | |
236 | ||
237 | printf("\"%s\" = ", m->id); | |
238 | fflush(stdout); | |
239 | ||
240 | tries = 0; | |
241 | ||
242 | do | |
243 | { | |
244 | httpClearFields(http); | |
245 | httpSetField(http, HTTP_FIELD_CONTENT_TYPE, | |
246 | "application/x-www-form-urlencoded"); | |
247 | httpSetField(http, HTTP_FIELD_CONTENT_LENGTH, length); | |
248 | ||
249 | if (httpPost(http, "/translate_t")) | |
250 | { | |
251 | httpReconnect(http); | |
252 | httpPost(http, "/translate_t"); | |
253 | } | |
254 | ||
a4d04587 | 255 | httpWrite2(http, buffer, bufptr - buffer); |
ef416fc2 | 256 | |
257 | while ((status = httpUpdate(http)) == HTTP_CONTINUE); | |
258 | ||
259 | if (status != HTTP_OK && status != HTTP_ERROR) | |
260 | httpFlush(http); | |
261 | ||
262 | tries ++; | |
263 | } | |
264 | while (status == HTTP_ERROR && tries < 10); | |
265 | ||
266 | if (status == HTTP_OK) | |
267 | { | |
268 | /* | |
269 | * OK, read the translation back... | |
270 | */ | |
271 | ||
272 | bufptr = buffer; | |
273 | bufend = buffer + sizeof(buffer) - 1; | |
274 | ||
a4d04587 | 275 | while ((bytes = httpRead2(http, bufptr, bufend - bufptr)) > 0) |
ef416fc2 | 276 | bufptr += bytes; |
277 | ||
278 | if (bytes < 0) | |
279 | { | |
280 | /* | |
281 | * Read error, abort! | |
282 | */ | |
283 | ||
284 | puts("READ ERROR!"); | |
285 | ret = 0; | |
286 | break; | |
287 | } | |
288 | ||
289 | *bufptr = '\0'; | |
290 | ||
291 | /* | |
49d87452 | 292 | * Find the div containing translation |
ef416fc2 | 293 | */ |
294 | ||
49d87452 | 295 | if ((bufptr = strstr(buffer, "<div id=result_box")) == NULL) |
ef416fc2 | 296 | { |
297 | /* | |
298 | * No textarea, abort! | |
299 | */ | |
300 | ||
49d87452 | 301 | puts("NO div id=result_box!"); |
ef416fc2 | 302 | ret = 0; |
303 | break; | |
304 | } | |
305 | ||
306 | if ((bufptr = strchr(bufptr, '>')) == NULL) | |
307 | { | |
308 | /* | |
309 | * textarea doesn't end, abort! | |
310 | */ | |
311 | ||
49d87452 | 312 | puts("DIV SHORT DATA!"); |
ef416fc2 | 313 | ret = 0; |
314 | break; | |
315 | } | |
316 | ||
317 | bufptr ++; | |
318 | ||
49d87452 | 319 | if ((bufend = strstr(bufptr, "</div>")) == NULL) |
ef416fc2 | 320 | { |
321 | /* | |
322 | * textarea doesn't close, abort! | |
323 | */ | |
324 | ||
49d87452 | 325 | puts("/DIV SHORT DATA!"); |
ef416fc2 | 326 | ret = 0; |
327 | break; | |
328 | } | |
329 | ||
330 | *bufend = '\0'; | |
331 | ||
332 | /* | |
333 | * Copy the translation... | |
334 | */ | |
335 | ||
336 | m->str = strdup(bufptr); | |
337 | ||
338 | /* | |
339 | * Convert character entities to regular chars... | |
340 | */ | |
341 | ||
342 | for (bufptr = strchr(m->str, '&'); | |
343 | bufptr; | |
344 | bufptr = strchr(bufptr + 1, '&')) | |
345 | { | |
346 | if (!strncmp(bufptr, "<", 4)) | |
347 | { | |
348 | *bufptr = '<'; | |
349 | _cups_strcpy(bufptr + 1, bufptr + 4); | |
350 | } | |
351 | else if (!strncmp(bufptr, ">", 4)) | |
352 | { | |
353 | *bufptr = '>'; | |
354 | _cups_strcpy(bufptr + 1, bufptr + 4); | |
355 | } | |
356 | else if (!strncmp(bufptr, "&", 5)) | |
357 | _cups_strcpy(bufptr + 1, bufptr + 5); | |
358 | } | |
359 | ||
360 | printf("\"%s\"\n", m->str); | |
361 | } | |
362 | else if (status == HTTP_ERROR) | |
363 | { | |
364 | printf("NETWORK ERROR (%s)!\n", strerror(httpError(http))); | |
365 | ret = 0; | |
366 | break; | |
367 | } | |
368 | else | |
369 | { | |
370 | printf("HTTP ERROR %d!\n", status); | |
371 | ret = 0; | |
372 | break; | |
373 | } | |
374 | } | |
375 | ||
376 | httpClose(http); | |
377 | ||
378 | return (ret); | |
379 | } | |
380 | ||
381 | ||
382 | /* | |
383 | * 'write_string()' - Write a quoted string to a file. | |
384 | */ | |
385 | ||
386 | int /* O - 1 on success, 0 on failure */ | |
387 | write_string(cups_file_t *fp, /* I - File to write to */ | |
388 | const char *s) /* I - String */ | |
389 | { | |
390 | while (*s) | |
391 | { | |
392 | switch (*s) | |
393 | { | |
394 | case '\n' : | |
395 | if (cupsFilePuts(fp, "\\n") < 0) | |
396 | return (0); | |
397 | break; | |
398 | ||
399 | case '\r' : | |
400 | if (cupsFilePuts(fp, "\\r") < 0) | |
401 | return (0); | |
402 | break; | |
403 | ||
404 | case '\t' : | |
405 | if (cupsFilePuts(fp, "\\t") < 0) | |
406 | return (0); | |
407 | break; | |
408 | ||
409 | case '\\' : | |
410 | if (cupsFilePuts(fp, "\\\\") < 0) | |
411 | return (0); | |
412 | break; | |
413 | ||
414 | case '\"' : | |
415 | if (cupsFilePuts(fp, "\\\"") < 0) | |
416 | return (0); | |
417 | break; | |
418 | ||
419 | default : | |
420 | if ((*s & 255) < ' ') | |
421 | { | |
422 | if (cupsFilePrintf(fp, "\\%o", *s) < 0) | |
423 | return (0); | |
424 | } | |
425 | else if (cupsFilePutChar(fp, *s) < 0) | |
426 | return (0); | |
427 | break; | |
428 | } | |
429 | ||
430 | s ++; | |
431 | } | |
432 | ||
433 | return (1); | |
434 | } | |
435 | ||
436 | ||
437 | /* | |
f2d18633 | 438 | * End of "$Id$". |
ef416fc2 | 439 | */ |