After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 253464 - Evolution should use ngettext for plural-forms
Evolution should use ngettext for plural-forms
Status: RESOLVED FIXED
Product: evolution
Classification: Applications
Component: general
1.5.x (obsolete)
Other other
: Normal blocker
: ---
Assigned To: JP Rosevear
Evolution QA team
: 253528 (view as bug list)
Depends on:
Blocks:
 
 
Reported: 2004-01-28 15:57 UTC by Danilo Segan
Modified: 2013-09-10 14:03 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
Use plural forms in Evolution -- part 1 of the patch (9.28 KB, patch)
2004-03-11 17:18 UTC, Danilo Segan
none Details | Review
Use plural forms in Evolution -- part 2 of the patch (again, untested) (2.05 KB, patch)
2004-03-11 21:02 UTC, Danilo Segan
none Details | Review
Use plural forms in Evolution -- part 2, attempt 2 (more brain-fart) -- I promise to stop spamming now (2.09 KB, patch)
2004-03-11 21:13 UTC, Danilo Segan
none Details | Review
Use ngettext in filter/filter-datespec.c -- second version of the patch (2.04 KB, patch)
2004-03-17 14:16 UTC, Danilo Segan
none Details | Review

Description Danilo Segan 2004-01-28 15:57:34 UTC
Introduction taken from http://bugzilla.gnome.org/show_bug.cgi?id=116236

As mentioned in
http://developer.gnome.org/doc/tutorials/gnome-i18n/developer.html#plurals,
the common way of handling plurals is broken for many locales. A way to
solve this is by using ngettext instead, as mentioned in that document. A
simple code example of code using ngettext:

  g_printf (ngettext ("Found %d file.", "Found %d files.", nbr_of_files),
nbr_of_files);


It was decided that Gnome 2.6 is the right time for requiring ngettext
support (most of the core components already use it, like Nautilus). I'm
also reminding you that string freeze time is February 9th, so this needs
to be fixed as soon as possible. I'm sorry to say that I don't have time to
work on providing a patch right now, though I may try to do it tommorow.


The following are relevant messages:

#: addressbook/gui/widgets/e-addressbook-model.c:151
msgid "1 contact"

#: addressbook/gui/widgets/e-addressbook-model.c:154
#, c-format
msgid "%d contacts"


#: addressbook/gui/widgets/eab-vcard-control.c:140
#, c-format
msgid "and %d other contacts."

#: addressbook/gui/widgets/eab-vcard-control.c:142
msgid "and one other contact."

#: calendar/gui/e-alarm-list.c:395
#, c-format
msgid "%d days"

#: calendar/gui/e-alarm-list.c:398
msgid "1 day"

#: calendar/gui/e-alarm-list.c:403
#, c-format
msgid "%d weeks"

#: calendar/gui/e-alarm-list.c:406
msgid "1 week"

#: calendar/gui/e-alarm-list.c:411
#, c-format
msgid "%d hours"

#: calendar/gui/e-alarm-list.c:414
msgid "1 hour"

#: calendar/gui/e-alarm-list.c:419
#, c-format
msgid "%d minutes"

#: calendar/gui/e-alarm-list.c:422
msgid "1 minute"

#: calendar/gui/e-alarm-list.c:427
#, c-format
msgid "%d seconds"

#: calendar/gui/e-alarm-list.c:430
msgid "1 second"


These are part of the Glade files, which means they cannot be easily
ngettext-ed:

#: calendar/gui/dialogs/alarm-options.glade.h:13 filter/filter.glade.h:18
msgid "hours"

#: calendar/gui/dialogs/alarm-options.glade.h:14 filter/filter.glade.h:19
msgid "minutes"

#: filter/filter.glade.h:20
msgid "months"

#: filter/filter.glade.h:21 mail/mail-config.glade.h:185
msgid "seconds"


Since both "%d" are standing for the same number, this one is not tricky
as it might seem at first:

#: addressbook/gui/widgets/eab-gui-util.c:204
#, c-format
msgid ""
"Opening %d contacts will open %d new windows as well.\n"
"Do you really want to display all of these contacts?"
Comment 1 Danilo Segan 2004-01-28 16:10:07 UTC
Whoops, I missed these.

#: calendar/gui/dialogs/delete-comp.c:131
#, c-format
msgid "Are you sure you want to delete %d appointments?"

#: calendar/gui/dialogs/delete-comp.c:136
#, c-format
msgid "Are you sure you want to delete %d tasks?"

#: calendar/gui/dialogs/delete-comp.c:141
#, c-format
msgid "Are you sure you want to delete %d journal entries?"

#: calendar/gui/e-itip-control.c:606
#, c-format
msgid "Every %d days"

#: calendar/gui/e-itip-control.c:614
#, c-format
msgid "Every %d weeks"

#: calendar/gui/e-itip-control.c:619
#, c-format
msgid "Every %d weeks on "

This one probably needs to have first letter capitalized as well.

#: calendar/gui/e-itip-control.c:657
#, c-format
msgid "every %d months"

#: calendar/gui/e-itip-control.c:665
#, c-format
msgid "Every %d years"

#: calendar/gui/e-itip-control.c:677
#, c-format
msgid " a total of %d times"

#: filter/filter-datespec.c:65
msgid "1 second ago"

#: filter/filter-datespec.c:65
#, c-format
msgid "%d seconds ago"

#: filter/filter-datespec.c:66
msgid "1 minute ago"

#: filter/filter-datespec.c:66
#, c-format
msgid "%d minutes ago"

#: filter/filter-datespec.c:67
msgid "1 hour ago"

#: filter/filter-datespec.c:67
#, c-format
msgid "%d hours ago"

#: filter/filter-datespec.c:68
msgid "1 day ago"

#: filter/filter-datespec.c:68
#, c-format
msgid "%d days ago"

#: filter/filter-datespec.c:69
msgid "1 week ago"

#: filter/filter-datespec.c:69
#, c-format
msgid "%d weeks ago"

#: filter/filter-datespec.c:70
msgid "1 month ago"

#: filter/filter-datespec.c:70
#, c-format
msgid "%d months ago"

#: filter/filter-datespec.c:71
msgid "1 year ago"

#: filter/filter-datespec.c:71
#, c-format
msgid "%d years ago"

#: mail/mail-ops.c:1818
#, c-format
msgid "Retrieving %d message(s)"

#: mail/mail-ops.c:1902
#, c-format
msgid "Saving %d messsage(s)"


As a sidenote, easiest way to fix all of these is, IMHO, to load
evolution/po/sr.po (Serbian translation) into Emacs PO-mode, search
for occurence of "bug: plural", and press "s" once you get to the
relevant message, which will open source code at correct position for you.
Comment 2 Not Zed 2004-03-11 08:58:11 UTC
I presume this means we should actually use something like

ngettext(N_("get %d file"), N_("get %d files"), n);

?
Comment 3 Danilo Segan 2004-03-11 16:26:48 UTC
Yeah.

Do you need any help in making a patch?  As I said, the fastest and
easiest way to do that is to open up sr.po in Emacs po-mode, search
for "bug: plural" and use "s" to "go to source reference" which will
open up the code at the right place.
Comment 4 Danilo Segan 2004-03-11 16:29:11 UTC
Uhm, actually no. The call should be like:
  ngettext("get %d file", "get %d files", n);

(no need to use N_, xgettext uses by default --keyword which
recognizes ngettext)
Comment 5 Danilo Segan 2004-03-11 17:18:22 UTC
Created attachment 43392 [details] [review]
Use plural forms in Evolution -- part 1 of the patch
Comment 6 Danilo Segan 2004-03-11 17:57:49 UTC
This is untested patch, and it doesn't handle the following a bit more
tricky (as in more thinking required) situation:

static const timespan timespans[] = {
	{ 1, N_("1 second ago"), N_("%d seconds ago"), 59.0 },
	{ 60, N_("1 minute ago"), N_("%d minutes ago"), 59.0 },
	{ 3600, N_("1 hour ago"), N_("%d hours ago"), 23.0 },
	{ 86400, N_("1 day ago"), N_("%d days ago"), 31.0 },
	{ 604800, N_("1 week ago"), N_("%d weeks ago"), 52.0 },
	{ 2419200, N_("1 month ago"), N_("%d months ago"), 12.0 },
	{ 31557600, N_("1 year ago"), N_("%d years ago"), 1000.0 },
};

The problem here is that we want to let xgettext know to extract these
into PO files as:
 msgid "%d minute ago"
 msgid_plural "%d minutes ago"

We can do that with introducing another noop for plural-forms (similar
to N_):
 #define NP_(a,b) 
and then create another table used simply to help xgettext extract
strings correctly:
 NP_("%d second ago", "%d seconds ago")
 NP_("%d minute ago", "%d minutes ago")
 NP_("%d hour ago", "%d hours ago")
 ...
(and also remove all N_ occurences above, they're not needed this way)

Next, we'd need to pass --keyword=NP_:1,2 to xgettext, and with
intltool we can currently do this in two ways: either from environment
variable or through XGETTEXT_KEYWORDS in po/Makefile.in.  None of
these is suitable for Evolution (there's no po/Makefile.in in CVS, and
translators won't like to have to set environment variable before
calling eg. "intltool-update de").  So, we'd have to patch intltool
(again, since we've done this recently for Gtk+, when
XGETTEXT_KEYWORDS was added).

In the code then, we'd have to change following snippet:

if (count == 1)
	/* 1 (minute|day|...) ago (singular time ago) */
	strcpy(buf, _(timespans[span].singular));
else
	/* N (minutes|days|...) ago (plural time ago) */
	sprintf(buf, _(timespans[span].plural), count);

to something like:

sprintf(buf, ngettext(timespans[span].singular,
timespans[span].plural, count), count);

I don't like this approach, but it requires slightest changes to the
code.  Other approach, which would not require changes to intltool is
to drop get_best_span and timespans array, and use ngettext in
ordinary ways.  Perhaps creating a function get_best_span_string(int
seconds) would be better, and keeping a timespans array without the
strings? (on the second thought, this seems like the best idea)

What do you think?
Comment 7 Danilo Segan 2004-03-11 18:02:39 UTC
uhm, replace above get_best_span_seconds with something like
get_string_for_span(int span, int count) {
	switch (span) {
	case 0:
            return g_strdup_printf(ngettext("%d second ago",
                                            "%d seconds ago",
                                            count), count);
        case 1:
             ....
}
Comment 8 Danilo Segan 2004-03-11 21:02:40 UTC
Created attachment 43395 [details] [review]
Use plural forms in Evolution -- part 2 of the patch (again, untested)
Comment 9 Danilo Segan 2004-03-11 21:05:59 UTC
These two patches should be sufficient.  I'll probably test them later
today (I first need to recompile all the other dependencies of
evolution).  So, I'm adding a "patch" keyword.
Comment 10 Danilo Segan 2004-03-11 21:09:35 UTC
and some brain-fart in second patch above: s/g_strdup_printf/g_strdup/g
Comment 11 Danilo Segan 2004-03-11 21:13:07 UTC
Created attachment 43396 [details] [review]
Use plural forms in Evolution -- part 2, attempt 2 (more brain-fart) -- I promise to stop spamming now
Comment 12 Gerardo Marin 2004-03-11 23:58:54 UTC
Upping prority so patch gets noticed.
Comment 13 Danilo Segan 2004-03-12 04:13:11 UTC
This patch at least compiles, and works in some instances I was able
to trigger (I've also got an updated Serbian translation which eases
testing at least for me, not sure how much it would help anyone else).

Unfortunately, I'm getting some crashes in Evolution (bug 255491), so
I'm unable to test it throughout.
Comment 14 Not Zed 2004-03-12 05:53:52 UTC
gerardo, patches get noticed by beign posted to evolution-patches ... :)

Danilo, could you please submit the first patch to evolution-patches?
 Please also include appropriate ChangeLog entries for the changes.

"Uhm, actually no. The call should be like:
  ngettext("get %d file", "get %d files", n);

(no need to use N_, xgettext uses by default --keyword which
recognizes ngettext)"

This means it looks for ngettext() as well as _() and N_() etc for
strings to snarf, i guess?

How do you put such strings in a table or file?  For example your
second patch should keep the strings in a table, not implement a nasty
switch statement.  Is using N_() like it did before adequate? (i'm
only responsible for the code in the second patch, and it must
continue to use a table before i'll accept it).


Comment 15 Danilo Segan 2004-03-12 08:03:06 UTC
Not Zed, thanks for looking at this.

Yes, xgettext (tool that extracts strings into translatable PO files,
which translators work on) looks at ngettext(a, b,...) as well. 

Still, a ngettext is a function call, so it cannot be put inside a
table, and by default xgettext doesn't look at anything like
ngettext_noop (like it does on "gettext_noop", "ngettext" or "N_"). 
Also, it requires two parameters, so the only way I see for keeping
this in a table is the ugly method I outlined above:
- rework table to be
#define NP_(a,b)  a, b
static const timespan timespans[] = {
	{ 1, NP_("%d second ago", "%d seconds ago"), 59.0 },
	{ 60, NP_("%d minute ago", "%d minutes ago"), 59.0 },
	{ 3600, NP_("%d hour ago", "%d hours ago"), 23.0 },
	{ 86400, NP_("%d day ago", "%d days ago"), 31.0 },
	{ 604800, NP_("%d week ago", "%d weeks ago"), 52.0 },
	{ 2419200, NP_("%d month ago", "%d months ago"), 12.0 },
	{ 31557600, NP_("%d year ago", "%d years ago"), 1000.0 },
};
(notice the very ugly hack here, where C preprocessor actually makes
this table appear to the compiler the same as it looks now, and this
may confuse many readers because two strings seem to be "related",
while they're separate fields in the table)

- change the relevant code to use ngettext (the snippet I change in
the patch):
sprintf(buf, ngettext(timespans[span].singular,
timespans[span].plural, count), count);

- pass --keyword=NP_:1,2 to xgettext; this is what makes this solution
"uglier" than what I propose.  

We'd need to patch intltool for this last step, and it means Evolution
would have to require latest intltool (which is not even released yet,
and there isn't even a patch that provides this functionality),
instead of being safe with even a year-old intltool.

I can provide a patch for intltool to be able to cope with this, since
I've commited some patches against it recently.  Still, some changes
in the build system would be required if you want this to work as
expected.  That's why I still opt for "ugly" solution using a switch
statement.  Also, patches won't be accepted for intltool before Gnome
2.6.0 release is made, so that means there won't be suitable intltool
for this at least until March 22nd.

If you want me to work on a patch for intltool, let me know so we can
discuss best ways to support it in the Evolution build system.

I think the approach with the switch statement is still the best
solution, because a static table is not a place to keep strings that
depend on a number in. Unless you're targetting English and Germanic
languages only, what I doubt is the case with Evolution.

Of course, I hope you won't even consider leaving this as is, because
in some languages that's like dropping "1 day", "1 month"... in
English, and having things like "1 days".

I'll add ChangeLog entry to the first patch and post it to
evolution-patches later today.
Comment 16 Danilo Segan 2004-03-14 04:29:06 UTC
Recently was added another string which requires usage of ngettext:

#: mail/mail-ops.c:695
#, c-format
msgid "Failed to send %d of %d messages"

ngettext should be used on the second of the numbers here.
Comment 17 Not Zed 2004-03-15 03:24:07 UTC
Why exactly do you need the NP_() stuff?

This is only to get the strings in the po files right?  What
difference would there be just using two N_()'s.

The function call will just be a plain function call.

Even your messy solution is neater than breaking out the table into a
case statment.
Comment 18 Danilo Segan 2004-03-15 03:54:53 UTC
(It is neater, apart from having to patch intltool :)

Yes, NP_ is needed to extract strings into PO files.  The catch here
is that there can be more than two plural forms (that's the entire
purpose of ngettext; for instance, Serbian and Russian have 3
different forms, Slovenian has 4! at the other extreme, there are
languages which have only one form, instead of 2 in English), so
related strings (one for "singular" and other for "plural") need to be
grouped together.  In PO files, instead of the regular two messages
N_() would give:
msgid "1 item"
msgstr ""

msgid "%d items"
msgstr ""

We end up with (for example, with 3 plural forms):
msgid "%d item"
msgid_plural "%d items"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""

If they're separate messages, xgettext doesn't know the relation
between them when extracting strings, and cannot produce messages like
this.  Which means that translators won't have them in PO files, even
though Evolution might be able to use them if they existed.

If it will be any easier to grasp, it's (in a way, not exactly)
similar to how 1st, 21st, 22nd, 33rd, and 77th are constructed in
English: it's not 33th or 22th.
Comment 19 Not Zed 2004-03-15 08:39:15 UTC
Various explitives deleted.

would this work:

#define ngettext(a, b) a, b
#define foo(a, b) ngettext(a, b)

use ngettext macro in the table
use foo in the code

Comment 20 Danilo Segan 2004-03-15 15:54:18 UTC
Yeah, it would work perhaps even easier than that:
Simply do:
#define ngettext(a,b) a, b
/* construct table here */
#undef ngettext
// ... and use ngettext(.singular,.plural,number) in the code.

Still, while this is a nice trick, it seems that we're getting to even
harder to understand code.  Though, it's your call.

(Also note that the call you #defined as "foo" above should have 3
parameters; xgettext looks only at the first two, so that's not a
problem; just pointing it out if you choose to use "foo" and only add
#undef ngettext before that)
Comment 21 Not Zed 2004-03-17 07:27:00 UTC
what if ngettext is already a macro though?

anyway, i guess its ok to assume it isn't.

well if it has 3 args, will the detection code find it if its only
used with 2?

if you care to, could you re-do a patch with the macro stuff in it?

otherwise i can do it if you're sick of this bit of the patch :)
 
Comment 22 Not Zed 2004-03-17 07:38:08 UTC
hmm, i think i accidentally changed the component
Comment 23 Danilo Segan 2004-03-17 10:27:25 UTC
I'll write the patch as explained above, but I'm also concerned with
the case of ngettext being a macro -- that happens when developers
want to support older gettextes which don't have ngettext function.

Currently, I'll simply use #ifdef ngettext and then #undef it prior to
this "modified" definition for the table -- that seems at least a bit
better if someone later makes ngettext a macro in Evolution if
function is not present (a compile or link time failure is way better
than having mysterious borkages during runtime).

Also, xgettext won't have problems extracting strings since it cares
only about parameters 1 and 2 in the function call; the real ngettext
function needs the number (3rd parameter) as well to determine which
of the strings to use.

I'll post a patch later today, so you could review it.
Comment 24 Danilo Segan 2004-03-17 14:16:17 UTC
Created attachment 43439 [details] [review]
Use ngettext in filter/filter-datespec.c -- second version of the patch
Comment 25 Danilo Segan 2004-03-17 14:20:17 UTC
Here's a patch along with the ChangeLog entry (patch will ignore it).
It does exactly as described above, it was tested to compile, and
xgettext/intltool extract messages as expected.

I've also added some comments, but you may strip them (or if I end up
commiting it, I may strip them before committing at your request).

Also, if you're satisfied with it, should it go to evolution-patches?

Thanks for the effort so far.
Comment 26 JP Rosevear 2004-03-17 16:57:08 UTC
*** bug 253528 has been marked as a duplicate of this bug. ***
Comment 27 Not Zed 2004-03-20 03:19:05 UTC
ok this patch looks good as is, please commit, or let me know we need to.

Maybe add a blank line after the #define ngettext stuff, but its not a
biggy.

thanks this!
Comment 28 Danilo Segan 2004-03-20 05:07:52 UTC
Ok, second patch commited (along with the newline, wow! :).
Comment 29 Danilo Segan 2004-03-24 00:33:16 UTC
Patch one applied to CVS HEAD as well. Marking as RESOLVED/FIXED.

(Uhm, I'm having some login problems with Bugzilla, I always need to
login twice, or rather, to reload this page once I login.)