1 /*
2  * Copyright (C) 2007  Ignacio Casal Quinteiro <nacho.resa@gmail.com>
3  *               2008  Igalia
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Authors:
19  *   Ignacio Casal Quinteiro <nacho.resa@gmail.com>
20  *   Pablo Sanxiao <psanxiao@gmail.com>
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 
27 #include "gtr-msg.h"
28 
29 #include <glib.h>
30 #include <glib-object.h>
31 #include <glib/gi18n.h>
32 #include <gtk/gtk.h>
33 #include <gettext-po.h>
34 
35 typedef struct
36 {
37   po_message_iterator_t iterator;
38 
39   po_message_t message;
40 
41   GtrMsgStatus status;
42 
43   gint po_position;
44 } GtrMsgPrivate;
45 
46 
47 G_DEFINE_TYPE_WITH_PRIVATE (GtrMsg, gtr_msg, G_TYPE_OBJECT)
48 
49 static gchar *message_error = NULL;
50 
51 static void
gtr_msg_recalc_status(GtrMsg * msg)52 gtr_msg_recalc_status (GtrMsg *msg)
53 {
54   /* Set the status */
55   if (gtr_msg_is_fuzzy (msg))
56     gtr_msg_set_status (msg, GTR_MSG_STATUS_FUZZY);
57   else if (gtr_msg_is_translated (msg))
58     gtr_msg_set_status (msg, GTR_MSG_STATUS_TRANSLATED);
59   else
60     gtr_msg_set_status (msg, GTR_MSG_STATUS_UNTRANSLATED);
61 }
62 
63 static void
gtr_msg_init(GtrMsg * msg)64 gtr_msg_init (GtrMsg * msg)
65 {
66 }
67 
68 static void
gtr_msg_finalize(GObject * object)69 gtr_msg_finalize (GObject * object)
70 {
71   G_OBJECT_CLASS (gtr_msg_parent_class)->finalize (object);
72 }
73 
74 static void
gtr_msg_class_init(GtrMsgClass * klass)75 gtr_msg_class_init (GtrMsgClass * klass)
76 {
77   GObjectClass *object_class = G_OBJECT_CLASS (klass);
78 
79   object_class->finalize = gtr_msg_finalize;
80 }
81 
82 /***************************** Public funcs ***********************************/
83 
84 /**
85  * gtr_msg_new:
86  * @iter: the po_message_iterator_t to set into the @msg
87  * @message: the
88  *
89  * Creates a new #GtrMsg.
90  *
91  * Return value: (transfer full): a new #GtrMsg object
92  **/
93 GtrMsg *
_gtr_msg_new(po_message_iterator_t iter,po_message_t message)94 _gtr_msg_new (po_message_iterator_t iter, po_message_t message)
95 {
96   GtrMsg *msg;
97 
98   g_return_val_if_fail (iter != NULL || message != NULL, NULL);
99 
100   msg = g_object_new (GTR_TYPE_MSG, NULL);
101 
102   _gtr_msg_set_iterator (msg, iter);
103   _gtr_msg_set_message (msg, message);
104 
105   gtr_msg_recalc_status (msg);
106 
107   return msg;
108 }
109 
110 /**
111  * gtr_msg_get_iterator:
112  * @msg: a #GtrMsg
113  *
114  * Return value: the message iterator in gettext format
115  **/
116 po_message_iterator_t
_gtr_msg_get_iterator(GtrMsg * msg)117 _gtr_msg_get_iterator (GtrMsg * msg)
118 {
119   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
120   g_return_val_if_fail (GTR_IS_MSG (msg), NULL);
121 
122   return priv->iterator;
123 }
124 
125 /**
126  * gtr_msg_set_iterator:
127  * @msg: a #GtrMsg
128  * @iter: the po_message_iterator_t to set into the @msg
129  *
130  * Sets the iterator into the #GtrMsg class.
131  **/
132 void
_gtr_msg_set_iterator(GtrMsg * msg,po_message_iterator_t iter)133 _gtr_msg_set_iterator (GtrMsg * msg, po_message_iterator_t iter)
134 {
135   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
136   g_return_if_fail (GTR_IS_MSG (msg));
137 
138   priv->iterator = iter;
139 }
140 
141 /**
142  * gtr_msg_get_message:
143  * @msg: a #GtrMsg
144  *
145  * Return value: the message in gettext format
146  **/
147 po_message_t
_gtr_msg_get_message(GtrMsg * msg)148 _gtr_msg_get_message (GtrMsg * msg)
149 {
150   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
151   g_return_val_if_fail (GTR_IS_MSG (msg), NULL);
152 
153   return priv->message;
154 }
155 
156 /**
157  * gtr_msg_set_message:
158  * @msg: a #GtrMsg
159  * @message: the po_message_t to set into the @msg
160  *
161  * Sets the message into the #GtrMsg class.
162  **/
163 void
_gtr_msg_set_message(GtrMsg * msg,po_message_t message)164 _gtr_msg_set_message (GtrMsg * msg, po_message_t message)
165 {
166   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
167   g_return_if_fail (GTR_IS_MSG (msg));
168   g_return_if_fail (message != NULL);
169 
170   priv->message = message;
171 }
172 
173 /**
174  * gtr_msg_is_translated:
175  * @msg: a #GtrMsg
176  *
177  * Gets whether or not the message is translated. See that a fuzzy message
178  * is also counted as translated so it must be checked first that the message
179  * is fuzzy.
180  *
181  * Return value: %TRUE if the message is translated
182  **/
183 gboolean
gtr_msg_is_translated(GtrMsg * msg)184 gtr_msg_is_translated (GtrMsg *msg)
185 {
186   g_return_val_if_fail (GTR_IS_MSG (msg), FALSE);
187 
188   if (gtr_msg_get_msgid_plural (msg) == NULL)
189     return gtr_msg_get_msgstr (msg)[0] != '\0';
190   else
191     {
192       gint i;
193 
194       for (i = 0;; i++)
195         {
196           const gchar *msgstr_i = gtr_msg_get_msgstr_plural (msg, i);
197           if (msgstr_i == NULL)
198             break;
199           if (msgstr_i[0] == '\0')
200             return FALSE;
201         }
202 
203       return TRUE;
204     }
205 }
206 
207 /**
208  * gtr_msg_is_fuzzy:
209  * @msg: a #GtrMsg
210  *
211  * Return value: TRUE if the message is fuzzy
212  **/
213 gboolean
gtr_msg_is_fuzzy(GtrMsg * msg)214 gtr_msg_is_fuzzy (GtrMsg * msg)
215 {
216   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
217   g_return_val_if_fail (GTR_IS_MSG (msg), FALSE);
218 
219   return po_message_is_fuzzy (priv->message);
220 }
221 
222 
223 /**
224  * gtr_msg_set_fuzzy:
225  * @msg: a #GtrMsg
226  * @fuzzy: the fuzzy value to set to the message
227  *
228  * Change the fuzzy mark of a message.
229  **/
230 void
gtr_msg_set_fuzzy(GtrMsg * msg,gboolean fuzzy)231 gtr_msg_set_fuzzy (GtrMsg * msg, gboolean fuzzy)
232 {
233   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
234   g_return_if_fail (GTR_IS_MSG (msg));
235 
236   po_message_set_fuzzy (priv->message, fuzzy);
237 }
238 
239 /**
240  * gtr_msg_set_status:
241  * @msg: a #GtrMsg
242  * @status: a #GtrMsgStatus
243  *
244  * Sets the status for a message.
245  */
246 void
gtr_msg_set_status(GtrMsg * msg,GtrMsgStatus status)247 gtr_msg_set_status (GtrMsg * msg, GtrMsgStatus status)
248 {
249   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
250   g_return_if_fail (GTR_IS_MSG (msg));
251 
252   priv->status = status;
253 }
254 
255 /**
256  * gtr_msg_get_status:
257  * @msg: a #GtrMsg
258  *
259  * Return value: the message's status.
260  */
261 GtrMsgStatus
gtr_msg_get_status(GtrMsg * msg)262 gtr_msg_get_status (GtrMsg * msg)
263 {
264   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
265   g_return_val_if_fail (GTR_IS_MSG (msg), 0);
266 
267   return priv->status;
268 }
269 
270 /**
271  * gtr_msg_get_msgid:
272  * @msg: a #GtrMsg
273  *
274  * Return value: the msgid (untranslated English string) of a message.
275  **/
276 const gchar *
gtr_msg_get_msgid(GtrMsg * msg)277 gtr_msg_get_msgid (GtrMsg * msg)
278 {
279   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
280   g_return_val_if_fail (GTR_IS_MSG (msg), NULL);
281 
282   return po_message_msgid (priv->message);
283 }
284 
285 
286 /**
287  * gtr_msg_get_msgid_plural:
288  * @msg: a #GtrMsg
289  *
290  * Return value: (transfer none): the msgid_plural (untranslated English plural
291  *               string) of a message, or NULL for a message without plural.
292  **/
293 const gchar *
gtr_msg_get_msgid_plural(GtrMsg * msg)294 gtr_msg_get_msgid_plural (GtrMsg * msg)
295 {
296   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
297   return po_message_msgid_plural (priv->message);
298 }
299 
300 
301 /**
302  * gtr_msg_get_msgstr:
303  * @msg: a #GtrMsg
304  *
305  * Return value: (transfer none): the msgstr (translation) of a message.
306  * Return the empty string for an untranslated message.
307  **/
308 const gchar *
gtr_msg_get_msgstr(GtrMsg * msg)309 gtr_msg_get_msgstr (GtrMsg * msg)
310 {
311   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
312   g_return_val_if_fail (GTR_IS_MSG (msg), NULL);
313 
314   return po_message_msgstr (priv->message);
315 }
316 
317 
318 /**
319  * gtr_msg_set_msgstr:
320  * @msg: a #GtrMsg
321  * @msgstr: the string to set in the @msg
322  *
323  * Change the msgstr (translation) of a message.
324  * Use an empty string to denote an untranslated message.
325  **/
326 void
gtr_msg_set_msgstr(GtrMsg * msg,const gchar * msgstr)327 gtr_msg_set_msgstr (GtrMsg * msg, const gchar * msgstr)
328 {
329   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
330   g_return_if_fail (GTR_IS_MSG (msg));
331   g_return_if_fail (msgstr != NULL);
332 
333   po_message_set_msgstr (priv->message, msgstr);
334 }
335 
336 
337 /**
338  * gtr_msg_get_msgstr_plural:
339  * @msg: a #GtrMsg
340  * @index: the index of the plural array
341  *
342  * Return value: (transfer none): the msgstr[index] for a message with plural
343  *               handling, or NULL when the index is out of range or for a
344  *               message without plural.
345  **/
346 const gchar *
gtr_msg_get_msgstr_plural(GtrMsg * msg,gint index)347 gtr_msg_get_msgstr_plural (GtrMsg * msg, gint index)
348 {
349   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
350   g_return_val_if_fail (GTR_IS_MSG (msg), NULL);
351 
352   return po_message_msgstr_plural (priv->message, index);
353 }
354 
355 /**
356  * gtr_msg_set_msgstr_plural:
357  * @msg: a #GtrMsg
358  * @index: the index where to set the msgstr
359  * @msgstr: the message to set in the msg
360  *
361  * Change the msgstr[index] for a message with plural handling.
362  * Use a NULL value at the end to reduce the number of plural forms.
363  **/
364 void
gtr_msg_set_msgstr_plural(GtrMsg * msg,gint index,const gchar * msgstr)365 gtr_msg_set_msgstr_plural (GtrMsg * msg, gint index, const gchar * msgstr)
366 {
367   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
368   g_return_if_fail (GTR_IS_MSG (msg));
369   g_return_if_fail (msgstr != NULL);
370 
371   po_message_set_msgstr_plural (priv->message, index, msgstr);
372 }
373 
374 
375 /**
376  * gtr_msg_get_comment:
377  * @msg: a #GtrMsg
378  *
379  * Return value: (transfer none) the comments for a message.
380  **/
381 const gchar *
gtr_msg_get_comment(GtrMsg * msg)382 gtr_msg_get_comment (GtrMsg * msg)
383 {
384   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
385   g_return_val_if_fail (GTR_IS_MSG (msg), NULL);
386 
387   return po_message_comments (priv->message);
388 }
389 
390 /**
391  * gtr_msg_set_comment:
392  * @msg: a #GtrMsg
393  * @comment: the comment to set for a message
394  *
395  * Change the comments for a message.
396  * comments should be a multiline string,
397  * ending in a newline, or empty.
398  **/
399 void
gtr_msg_set_comment(GtrMsg * msg,const gchar * comment)400 gtr_msg_set_comment (GtrMsg * msg, const gchar * comment)
401 {
402   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
403   g_return_if_fail (GTR_IS_MSG (msg));
404   g_return_if_fail (comment != NULL);
405 
406   po_message_set_comments (priv->message, comment);
407 }
408 
409 /**
410  * gtr_msg_get_po_position:
411  * @msg: a #GtrMsg
412  *
413  * Return value: the position of the message.
414  *
415  * Gets the position of this message in the PO file in relation to the other
416  * messages.
417  **/
418 gint
gtr_msg_get_po_position(GtrMsg * msg)419 gtr_msg_get_po_position (GtrMsg * msg)
420 {
421   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
422   g_return_val_if_fail (GTR_IS_MSG (msg), 0);
423 
424   return priv->po_position;
425 }
426 
427 /**
428  * gtr_msg_set_po_position:
429  * @msg: a #GtrMsg
430  * @po_position: the numerical position of the message.
431  *
432  * Sets the numerical position of this message in relation to other messages.
433  **/
434 void
gtr_msg_set_po_position(GtrMsg * msg,gint po_position)435 gtr_msg_set_po_position (GtrMsg * msg, gint po_position)
436 {
437   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
438   g_return_if_fail (GTR_IS_MSG (msg));
439 
440   priv->po_position = po_position;
441 }
442 
443 /**
444  * gtr_msg_get_extracted_comments:
445  * @msg: a #GtrMsg
446  *
447  * Return value: (transfer none): the extracted comments for a message.
448  **/
449 const gchar *
gtr_msg_get_extracted_comments(GtrMsg * msg)450 gtr_msg_get_extracted_comments (GtrMsg * msg)
451 {
452   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
453   g_return_val_if_fail (GTR_IS_MSG (msg), NULL);
454 
455   return po_message_extracted_comments (priv->message);
456 }
457 
458 /**
459  * gtr_msg_get_filename:
460  * @msg: a #GtrMsg
461  * @i: the i-th file for a message.
462  *
463  * Return value: (transfer none): the i-th filename for a message, or NULL if
464  *               @i is out of range.
465  */
466 const gchar *
gtr_msg_get_filename(GtrMsg * msg,gint i)467 gtr_msg_get_filename (GtrMsg * msg, gint i)
468 {
469   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
470   g_return_val_if_fail (GTR_IS_MSG (msg), NULL);
471 
472   po_filepos_t filepos;
473 
474   filepos = po_message_filepos (priv->message, i);
475 
476   if (filepos == NULL)
477     return NULL;
478 
479   return po_filepos_file (filepos);
480 }
481 
482 /**
483  * gtr_msg_get_file_line:
484  * @msg: a #GtrMsg
485  * @i: the i-th file for a message.
486  *
487  * Return value: (transfer none): the i-th file line for a message, or NULL if
488  *               @i is out of range.
489  */
490 gint *
gtr_msg_get_file_line(GtrMsg * msg,gint i)491 gtr_msg_get_file_line (GtrMsg * msg, gint i)
492 {
493   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
494   g_return_val_if_fail (GTR_IS_MSG (msg), (gint *) 0);
495 
496   po_filepos_t filepos;
497 
498   filepos = po_message_filepos (priv->message, i);
499 
500   if (filepos == NULL)
501     return NULL;
502 
503   return (gint *) po_filepos_start_line (filepos);
504 }
505 
506 /**
507  * gtr_msg_get_msgctxt:
508  * @msg: a #GtrMsg
509  *
510  * Return value: (transfer none): the context of a message, or NULL for a
511  *               message not restricted to a context.
512  */
513 const gchar *
gtr_msg_get_msgctxt(GtrMsg * msg)514 gtr_msg_get_msgctxt (GtrMsg * msg)
515 {
516   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
517   g_return_val_if_fail (GTR_IS_MSG (msg), NULL);
518 
519   return po_message_msgctxt (priv->message);
520 }
521 
522 /**
523  * gtr_msg_get_format:
524  * @msg: a #GtrMsg
525  *
526  * Return the pretty name associated with a format type.
527  * For example, for "csharp-format", return "C#".
528  * Return NULL if the are no format type in the message.
529  *
530  * Return value: (transfer none): the pretty name associated with a format type
531  *               or NULL if the message hasn't any format type.
532  */
533 const gchar *
gtr_msg_get_format(GtrMsg * msg)534 gtr_msg_get_format (GtrMsg * msg)
535 {
536   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
537   const gchar *const *format_list;
538   gint i;
539 
540   g_return_val_if_fail (GTR_IS_MSG (msg), NULL);
541 
542   format_list = po_format_list ();
543 
544   for (i = 0; format_list[i] != NULL; i++)
545     {
546       if (po_message_is_format (priv->message, format_list[i]))
547         return po_format_pretty_name (format_list[i]);
548     }
549 
550   return NULL;
551 }
552 
553 /*
554  * Functions to manage the gettext errors
555  */
556 static void
on_gettext_po_xerror(gint severity,po_message_t message,const gchar * filename,size_t lineno,size_t column,gint multiline_p,const gchar * message_text)557 on_gettext_po_xerror (gint severity,
558                       po_message_t message,
559                       const gchar * filename, size_t lineno, size_t column,
560                       gint multiline_p, const gchar * message_text)
561 {
562   if (message_text)
563     message_error = g_strdup (message_text);
564   else
565     message_error = NULL;
566 }
567 
568 static void
on_gettext_po_xerror2(gint severity,po_message_t message1,const gchar * filename1,size_t lineno1,size_t column1,gint multiline_p1,const gchar * message_text1,po_message_t message2,const gchar * filename2,size_t lineno2,size_t column2,gint multiline_p2,const gchar * message_text2)569 on_gettext_po_xerror2 (gint severity,
570                        po_message_t message1,
571                        const gchar * filename1, size_t lineno1,
572                        size_t column1, gint multiline_p1,
573                        const gchar * message_text1, po_message_t message2,
574                        const gchar * filename2, size_t lineno2,
575                        size_t column2, gint multiline_p2,
576                        const gchar * message_text2)
577 {
578   g_warning ("Error: %s.\n %s", message_text1, message_text2);
579 }
580 
581 /**
582  * gtr_msg_check:
583  * @msg: a #GtrMsg
584  *
585  * Test whether the message translation is a valid format string if the message
586  * is marked as being a format string.
587  *
588  * Return value: (transfer full): the message error or NULL if there is not any
589  *               error. Must be freed with g_free.
590  **/
591 gchar *
gtr_msg_check(GtrMsg * msg)592 gtr_msg_check (GtrMsg * msg)
593 {
594   gchar *error = NULL;
595   GtrMsgPrivate *priv = gtr_msg_get_instance_private (msg);
596   struct po_xerror_handler handler;
597 
598   g_return_val_if_fail (msg != NULL, NULL);
599 
600   /* We are not freeing the message_error so at start should be NULL
601    * always for us
602    */
603   message_error = NULL;
604 
605   handler.xerror = &on_gettext_po_xerror;
606   handler.xerror2 = &on_gettext_po_xerror2;
607 
608   po_message_check_all (priv->message, priv->iterator, &handler);
609 
610   if (gtr_msg_is_fuzzy (msg) || !gtr_msg_is_translated (msg))
611     {
612       if (message_error)
613         g_free (message_error);
614       message_error = NULL;
615     }
616 
617   if (message_error)
618     {
619       error = g_strdup (message_error);
620       g_free (message_error);
621     }
622   message_error = NULL;
623 
624   return error;
625 }
626