1 /*
2 * Convert a GNU gettext .po file to an Apple .strings file.
3 *
4 * Copyright © 2020-2024 by OpenPrinting.
5 * Copyright 2007-2017 by Apple Inc.
6 *
7 * Licensed under Apache License v2.0. See the file "LICENSE" for more information.
8 *
9 * Usage:
10 *
11 * po2strings filename.strings filename.po
12 *
13 * Compile with:
14 *
15 * gcc -o po2strings po2strings.c `cups-config --libs`
16 */
17
18 #include <cups/cups-private.h>
19
20
21 /*
22 * The .strings file format is simple:
23 *
24 * // comment
25 * "msgid" = "msgstr";
26 *
27 * The GNU gettext .po format is also fairly simple:
28 *
29 * #. comment
30 * msgid "some text"
31 * msgstr "localized text"
32 *
33 * The comment, msgid, and msgstr text can span multiple lines using the form:
34 *
35 * #. comment
36 * #. more comments
37 * msgid ""
38 * "some long text"
39 * msgstr ""
40 * "localized text spanning "
41 * "multiple lines"
42 *
43 * Both the msgid and msgstr strings use standard C quoting for special
44 * characters like newline and the double quote character.
45 */
46
47 static char *normalize_string(const char *idstr, char *buffer, size_t bufsize);
48
49
50 /*
51 * main() - Convert .po file to .strings.
52 */
53
54 int /* O - Exit code */
main(int argc,char * argv[])55 main(int argc, /* I - Number of command-line args */
56 char *argv[]) /* I - Command-line arguments */
57 {
58 int i; /* Looping var */
59 const char *pofile, /* .po filename */
60 *stringsfile; /* .strings filename */
61 cups_file_t *po, /* .po file */
62 *strings; /* .strings file */
63 char s[4096], /* String buffer */
64 *ptr, /* Pointer into buffer */
65 *temp, /* New string */
66 *msgid, /* msgid string */
67 *msgstr, /* msgstr string */
68 normalized[8192];/* Normalized msgid string */
69 size_t length; /* Length of combined strings */
70 int use_msgid; /* Use msgid strings for msgstr? */
71
72
73 /*
74 * Process command-line arguments...
75 */
76
77 pofile = NULL;
78 stringsfile = NULL;
79 use_msgid = 0;
80
81 for (i = 1; i < argc; i ++)
82 {
83 if (!strcmp(argv[i], "-m"))
84 use_msgid = 1;
85 else if (argv[i][0] == '-')
86 {
87 puts("Usage: po2strings [-m] filename.po filename.strings");
88 return (1);
89 }
90 else if (!pofile)
91 pofile = argv[i];
92 else if (!stringsfile)
93 stringsfile = argv[i];
94 else
95 {
96 puts("Usage: po2strings [-m] filename.po filename.strings");
97 return (1);
98 }
99 }
100
101 if (!pofile || !stringsfile)
102 {
103 puts("Usage: po2strings [-m] filename.po filename.strings");
104 return (1);
105 }
106
107 /*
108 * Read strings from the .po file and write to the .strings file...
109 */
110
111 if ((po = cupsFileOpen(pofile, "r")) == NULL)
112 {
113 perror(pofile);
114 return (1);
115 }
116
117 if ((strings = cupsFileOpen(stringsfile, "w")) == NULL)
118 {
119 perror(stringsfile);
120 cupsFileClose(po);
121 return (1);
122 }
123
124 msgid = msgstr = NULL;
125
126 while (cupsFileGets(po, s, sizeof(s)) != NULL)
127 {
128 if (s[0] == '#' && s[1] == '.')
129 {
130 /*
131 * Copy comment string...
132 */
133
134 if (msgid && msgstr)
135 {
136 /*
137 * First output the last localization string...
138 */
139
140 if (*msgid)
141 cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid,
142 (use_msgid || !*msgstr) ? msgid : msgstr);
143
144 free(msgid);
145 free(msgstr);
146 msgid = msgstr = NULL;
147 }
148
149 cupsFilePrintf(strings, "//%s\n", s + 2);
150 }
151 else if (s[0] == '#' || !s[0])
152 {
153 /*
154 * Skip blank and file comment lines...
155 */
156
157 continue;
158 }
159 else
160 {
161 /*
162 * Strip the trailing quote...
163 */
164
165 if ((ptr = strrchr(s, '\"')) == NULL)
166 continue;
167
168 *ptr = '\0';
169
170 /*
171 * Find start of value...
172 */
173
174 if ((ptr = strchr(s, '\"')) == NULL)
175 continue;
176
177 ptr ++;
178
179 /*
180 * Create or add to a message...
181 */
182
183 if (!strncmp(s, "msgid", 5))
184 {
185 /*
186 * Output previous message as needed...
187 */
188
189 if (msgid && msgstr)
190 {
191 if (*msgid)
192 cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid, normalize_string((use_msgid || !*msgstr) ? msgid : msgstr, normalized, sizeof(normalized)));
193 }
194
195 if (msgid)
196 free(msgid);
197
198 if (msgstr)
199 free(msgstr);
200
201 msgid = strdup(ptr);
202 msgstr = NULL;
203 }
204 else if (s[0] == '\"' && (msgid || msgstr))
205 {
206 /*
207 * Append to current string...
208 */
209
210 size_t ptrlen = strlen(ptr); /* Length of string */
211
212 length = strlen(msgstr ? msgstr : msgid);
213
214 if ((temp = realloc(msgstr ? msgstr : msgid,
215 length + ptrlen + 1)) == NULL)
216 {
217 free(msgid);
218 if (msgstr)
219 free(msgstr);
220 perror("Unable to allocate string");
221 return (1);
222 }
223
224 if (msgstr)
225 {
226 /*
227 * Copy the new portion to the end of the msgstr string - safe
228 * to use strcpy because the buffer is allocated to the correct
229 * size...
230 */
231
232 msgstr = temp;
233
234 memcpy(msgstr + length, ptr, ptrlen + 1);
235 }
236 else
237 {
238 /*
239 * Copy the new portion to the end of the msgid string - safe
240 * to use strcpy because the buffer is allocated to the correct
241 * size...
242 */
243
244 msgid = temp;
245
246 memcpy(msgid + length, ptr, ptrlen + 1);
247 }
248 }
249 else if (!strncmp(s, "msgstr", 6) && msgid)
250 {
251 /*
252 * Set the string...
253 */
254
255 if (msgstr)
256 free(msgstr);
257
258 if ((msgstr = strdup(ptr)) == NULL)
259 {
260 free(msgid);
261 perror("Unable to allocate msgstr");
262 return (1);
263 }
264 }
265 }
266 }
267
268 if (msgid && msgstr)
269 {
270 if (*msgid)
271 cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid, normalize_string((use_msgid || !*msgstr) ? msgid : msgstr, normalized, sizeof(normalized)));
272 }
273
274 if (msgid)
275 free(msgid);
276
277 if (msgstr)
278 free(msgstr);
279
280 cupsFileClose(po);
281 cupsFileClose(strings);
282
283 return (0);
284 }
285
286
287 /*
288 * 'normalize_string()' - Normalize a msgid string.
289 *
290 * This function converts ASCII ellipsis and double quotes to their Unicode
291 * counterparts.
292 */
293
294 static char * /* O - Normalized string */
normalize_string(const char * idstr,char * buffer,size_t bufsize)295 normalize_string(const char *idstr, /* I - msgid string */
296 char *buffer, /* I - Normalized string buffer */
297 size_t bufsize) /* I - Size of string buffer */
298 {
299 char *bufptr = buffer, /* Pointer into buffer */
300 *bufend = buffer + bufsize - 3; /* End of buffer */
301 int quote = 0, /* Quote direction */
302 html = 0; /* HTML text */
303
304
305 while (*idstr && bufptr < bufend)
306 {
307 if (!strncmp(idstr, "<A ", 3))
308 html = 1;
309 else if (html && *idstr == '>')
310 html = 0;
311
312 if (*idstr == '.' && idstr[1] == '.' && idstr[2] == '.')
313 {
314 /*
315 * Convert ... to Unicode ellipsis...
316 */
317
318 *bufptr++ = (char)0xE2;
319 *bufptr++ = (char)0x80;
320 *bufptr++ = (char)0xA6;
321 idstr += 2;
322 }
323 else if (!html && *idstr == '\\' && idstr[1] == '\"')
324 {
325 if (quote)
326 {
327 /*
328 * Convert second \" to Unicode right (curley) double quote.
329 */
330
331 *bufptr++ = (char)0xE2;
332 *bufptr++ = (char)0x80;
333 *bufptr++ = (char)0x9D;
334 quote = 0;
335 }
336 else if (strchr(idstr + 2, '\"') != NULL)
337 {
338 /*
339 * Convert first \" to Unicode left (curley) double quote.
340 */
341
342 *bufptr++ = (char)0xE2;
343 *bufptr++ = (char)0x80;
344 *bufptr++ = (char)0x9C;
345 quote = 1;
346 }
347 else
348 {
349 /*
350 * Convert lone \" to Unicode double prime.
351 */
352
353 *bufptr++ = (char)0xE2;
354 *bufptr++ = (char)0x80;
355 *bufptr++ = (char)0xB3;
356 }
357
358 idstr ++;
359 }
360 else if (*idstr == '\'')
361 {
362 if (strchr(idstr + 1, '\'') == NULL || quote)
363 {
364 /*
365 * Convert second ' (or ' used for a contraction) to Unicode right
366 * (curley) single quote.
367 */
368
369 *bufptr++ = (char)0xE2;
370 *bufptr++ = (char)0x80;
371 *bufptr++ = (char)0x99;
372 quote = 0;
373 }
374 else
375 {
376 /*
377 * Convert first ' to Unicode left (curley) single quote.
378 */
379
380 *bufptr++ = (char)0xE2;
381 *bufptr++ = (char)0x80;
382 *bufptr++ = (char)0x98;
383 quote = 1;
384 }
385 }
386 else
387 *bufptr++ = *idstr;
388
389 idstr ++;
390 }
391
392 *bufptr = '\0';
393
394 return (buffer);
395 }
396