Merge lp:~adiroiban/launchpad/bug-201749 into lp:launchpad

Proposed by Adi Roiban
Status: Merged
Approved by: Henning Eggers
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~adiroiban/launchpad/bug-201749
Merge into: lp:launchpad
Diff against target: 926 lines (+608/-86)
11 files modified
lib/canonical/launchpad/javascript/translations/pofile.js (+119/-18)
lib/lp/translations/browser/tests/translationmessage-views.txt (+98/-0)
lib/lp/translations/browser/translationmessage.py (+17/-0)
lib/lp/translations/interfaces/potmsgset.py (+12/-0)
lib/lp/translations/model/potmsgset.py (+41/-13)
lib/lp/translations/model/translationmessage.py (+2/-2)
lib/lp/translations/stories/standalone/xx-pofile-translate-needs-review-flags-preserved.txt (+62/-3)
lib/lp/translations/templates/pofile-translate.pt (+1/-27)
lib/lp/translations/templates/translationmessage-translate.pt (+1/-19)
lib/lp/translations/tests/test_potmsgset.py (+83/-0)
lib/lp/translations/windmill/tests/test_pofile_translate.py (+172/-4)
To merge this branch: bzr merge lp:~adiroiban/launchpad/bug-201749
Reviewer Review Type Date Requested Status
Henning Eggers (community) code Approve
Review via email: mp+21250@code.launchpad.net

Commit message

Allow reseting the review state for local suggestions of a potmsgset. Landed by henninge.

Description of the change

= Bug 201749 =
Take for example this string:

https://translations.edge.launchpad.net/ubuntu/hardy/+source/system-config-printer/+pots/system-config-printer/it/287/+translate

This has been translated by an approved translator, but it's impossibile
to set it with the "Someone should review this translation". I did try a
moment ago, setting it and the filtering with the "need review" filter
in the "Show" drop-down list.

Another example:

https://translations.edge.launchpad.net/ubuntu/hardy/+source/ubuntu-docs/+pots/desktop-effects/it/+translate?show=all&start=10

I marked with "Someone should..." number 15 and 16 and saved, but they
never show up with the "need review" filter.

== Proposed fix ==
If a reviewer is only ticking „needs review”, or submitting an empty translations together with marking it as „needs review”, reset the review state for the current message so that all suggestions can be viewed.

Submitting a new translation (different than the current one) will add it as a new suggestion and will not reset the reviewed translations.

== Pre-implementation notes ==
The logs are pasted on this bug report
https://bugs.edge.launchpad.net/rosetta/+bug/201749

== Implementation details ==
potmsgset.resetCurrentTranslation method has the pofile and suggestion as argument to simplify writing the tests.

translationmessage._storeTranslations already contains a lot of logic and will make those tests very complicated.

I am not sure what changes should I make on a potmsgset to be listed again as „needs review”.
The current implementation just updated the date_created ... but maybe there is a better way of doing that without faking the date_created.

== Tests ==
./bin/test -t potmsgset -t translationmessage

== Demo and Q/A ==

Login in launchpad translation as an reviewer (or admin).
Go to a translation, ie:
https://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/man/es/1/+translate

Submit a new translation ie „some bla bla”, without ticking the „Someone should review this translation” checkbox.
Press „Save and continue” and you should see that „some bla bla” is the current translation.

Not submit a new translation „other bla bla”, but this time tick „Someone should review this translation”.
The current translation should remain „some bla bla” but you should see „other bla bla” listed as a suggestion.

Tick „Confirm this translation and dismiss all suggestions.”
„some bla bla” should be the current translation, why there will be no suggestions.

Next, tick „Someone should review this translation” but without adding any new translation.
The current translation should be empty, while „some bla bla” and „other bla bla” are listed as suggestions.

Add a new translation „diverged bla bla” without ticking „Someone should review this translation”.
You should see that „diverged bla bla” is the current translation, and for the new suggestion you only have the „Someone should review this translation” option, while „Make this translation specific to Ubuntu hoary” is no longer available.

Reset the translation one again by submitting a new empty translation „” (the empty string) and ticking „Someone should review this translation”.
You should see that current translation is empty, while all previously entered translations are listed as suggestions.

To post a comment you must log in.
Revision history for this message
Henning Eggers (henninge) wrote :
Download full text (22.0 KiB)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Am 12.03.2010 16:42, Adi Roiban schrieb:
Hi Adi,
thanks for tackling this. There seem to have been some misunderstanding
about how this option worked, judging by the comments in the bug.

I am a little concerned about the behaviour. Shouldn't the radio button
in front of the input box need to be activated (see below)? Also, I am
not sure if really _all_ old suggestions should be brought up again. But
that may be OK.

I also have some other suggestions. Please look at them and we can talk
again tomorrow.

 review needs-fixing

Please find my comments below.

Cheers,
Hennning

> === modified file 'lib/lp/translations/browser/tests/translationmessage-views.txt'
> --- lib/lp/translations/browser/tests/translationmessage-views.txt 2009-09-11 22:24:51 +0000
> +++ lib/lp/translations/browser/tests/translationmessage-views.txt 2010-03-15 13:43:34 +0000
> @@ -689,3 +689,108 @@
> False
> >>> subview.shared_translationmessage == translationmessage
> True
> +
> +
> +Reseting current translation

Resetting ...

> +-----------------------------
> +
> +The current translation can be resetted by submiting an empty translation or
> +a translation similar to the current one, while forcing the suggestions.

The current translation can be reset by submitting an empty translation
or a translation similar to the current one while forcing suggestions.

> +
> + >>> # We need to force timestamp update, since suggestions are submitted
> + >>> # to fast

Please don't put comments in doc tests. Whatever goes into the comment
can go into the normal text. It's "too fast", btw. ;)

> + >>> year_tick = 2100
> + >>> def submit_translation(translation, force_suggestion=False):
> + ... global year_tick
> + ... translationmessage = TranslationMessage.get(1)

I find this very obscure. I think it would be clearer to pass
translationmessage as a parameter into the function. If you want "any"
TranslationMessage object, use the factory outside and pass the object
into the function.

> + ... pofile = translationmessage.pofile
> + ... potmsgset = translationmessage.potmsgset
> + ... server_url = '/'.join(
> + ... [canonical_url(translationmessage), '+translate'])
> + ... now = (str(year_tick) +
> + ... datetime.now(UTC).strftime('-%m-%dT%H:%M:%S+00:00'))
> + ... year_tick += 1
> + ... form = {
> + ... 'lock_timestamp': now,
> + ... 'alt': None,
> + ... 'msgset_1': None,
> + ... 'msgset_1_es_translation_0_radiobutton':
> + ... 'msgset_1_es_translation_0_new',
> + ... 'msgset_1_es_translation_0_new': translation,
> + ... 'submit_translations': 'Save & Continue'}
> + ... if force_suggestion:
> + ... form['msgset_1_es_needsreview'] = 'force_suggestion'
> + ... tm_view = create_view(
> + ... translationmessage, "+translate", form=form,
> + ... layer=TranslationsLayer, server_url=server_url)
> + ... tm_view.request.method = 'POST'
> + ... tm_view.initialize()
> +
> + >>> def print_current_trans...

review: Needs Fixing
Revision history for this message
Данило Шеган (danilo) wrote :

Henning said:

> So, this brings up all old translations? Is that really what we want?

I suggested Adi work in phases: this _might_ be the first step, but the natural next step would be to set the newly set empty message as "date_reviewed = previous_current.date_created - timedelta(0,1)" where we wouldn't really show all suggestions, but only those created after the previous current. Not perfect, but at least we won't have to forge the date_created on the previous_current so it shows up in the list of suggestions.

That's the other option, without making this too complex, but I leave that to the two of you to decide which is better.

Revision history for this message
Adi Roiban (adiroiban) wrote :
Download full text (22.9 KiB)

> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> Am 12.03.2010 16:42, Adi Roiban schrieb:
> Hi Adi,
> thanks for tackling this. There seem to have been some misunderstanding
> about how this option worked, judging by the comments in the bug.
>
> I am a little concerned about the behaviour. Shouldn't the radio button
> in front of the input box need to be activated (see below)? Also, I am
> not sure if really _all_ old suggestions should be brought up again. But
> that may be OK.
This implementation should reset the current translation as there is no other workaround for the following use case:
https://bugs.edge.launchpad.net/rosetta/+bug/201749/comments/14

In an ideal world, we would have a "is_reviewed" field for each translation message, a dedicated "reset translation" checkbox and a way to see all previously entered suggestions.

I was thinking there is no need to force a user to add an empty text and mark it as a suggestion and it should be enough to just tick the "needs review" checkbox.

> I also have some other suggestions. Please look at them and we can talk
> again tomorrow.
Many thanks for your review.

Please see my inline comments.

[snip]
> > +Reseting current translation
>
> Resetting ...
Fixed.

> > +-----------------------------
> > +
> > +The current translation can be resetted by submiting an empty translation
> or
> > +a translation similar to the current one, while forcing the suggestions.
>
> The current translation can be reset by submitting an empty translation
> or a translation similar to the current one while forcing suggestions.

Fixed.

> > +
> > + >>> # We need to force timestamp update, since suggestions are
> submitted
> > + >>> # to fast
>
> Please don't put comments in doc tests. Whatever goes into the comment
> can go into the normal text. It's "too fast", btw. ;)

Done.

> > + >>> year_tick = 2100
> > + >>> def submit_translation(translation, force_suggestion=False):
> > + ... global year_tick
> > + ... translationmessage = TranslationMessage.get(1)
>
> I find this very obscure. I think it would be clearer to pass
> translationmessage as a parameter into the function. If you want "any"
> TranslationMessage object, use the factory outside and pass the object
> into the function.
I was getting the translation message with id 1 to simplify the form submission, since
the form names depend on the id, and also since translation permission were already there.

I have rewritten the code to take a translation message as parameter and only use generated data.
[snip]
> > +
> > +Same happens if we force the submission of a suggestion similar with the
>
> ... similar to the ...

Done.

[snip]
> > + if (force_suggestion and
> > + self.user_is_official_translator and
> > + (self._areSuggestionsEmpty(translations) or
> > + (translationmessage is not None and
> > + self._areSuggestionsIdenticalToTranslations(translations,
> > + translationmessage.translations)))):
>
> This is not very easy to read, right? I suggest you create a few
> intermediate boolean values so that they all fit on one li...

Revision history for this message
Henning Eggers (henninge) wrote :

Thanks Adi,
you have done a good job restructering the test, I did not intend to put that much extra work on you. Thanks.

Two suggestions on the patch:
 * msgset_stem is not clear to me. Is that a Hungarian word there? ;-) Just msgset_id would be clearer, I gues.
 * Maybe _isNoTranslationConflict is not really a boolean function? The way you use it now, i.e. outside an "if" statement looks much better. I'd suggest doing the same thing at the other call site and rename it to "_maybeRaiseTranslationConflict" or "_checkTranslationConflict". I prefer the first because it is clear that this may raise an exception.

Ok, about the remaining problem.
> I was thinking there is no need to force a user to add an empty
> text and mark it as a suggestion and it should be enough to just
> tick the "needs review" checkbox.

Yes, we agree here. But still the radio button should move to the empty input field, I think. This way it is clear that the current translation will change - to nothing.
There is this big misunderstanding that you and others had about the meaning of "Someone should review..." because it was always only intended for newly entered translations. The use that you added now is a new feature, not a fix of a buggy feature.
That is why I think moving the radio button down to the input field will keep the original intention that the checkbox relates to the input box, not the current translation, while adding the extra feature of resetting the current translatoin.

I know this requires some javascript. I don't know how comfortable you are with writing that. If you want to you can file an extra bug for it which should be fixed this cycle. You can land this branch as it is (see above suggestions) but it would be great if that extra javascript would be in it. ;-)

Cheers,
Henning

review: Approve (code)
Revision history for this message
Adi Roiban (adiroiban) wrote :
Download full text (8.1 KiB)

> Thanks Adi,
> you have done a good job restructering the test, I did not intend to put that
> much extra work on you. Thanks.
>
> Two suggestions on the patch:
> * msgset_stem is not clear to me. Is that a Hungarian word there? ;-) Just
> msgset_id would be clearer, I gues.

I have renamed it to msgset_id.
I was using „stem” as root, (linguistics) the form of a word after all affixes are removed; "thematic vowels are part of the stem"

> * Maybe _isNoTranslationConflict is not really a boolean function? The way
> you use it now, i.e. outside an "if" statement looks much better. I'd suggest
> doing the same thing at the other call site and rename it to
> "_maybeRaiseTranslationConflict" or "_checkTranslationConflict". I prefer the
> first because it is clear that this may raise an exception.

Renamed to _maybeRaiseTranslationConflict and moved it outside of if-statement.

> Ok, about the remaining problem.
> > I was thinking there is no need to force a user to add an empty
> > text and mark it as a suggestion and it should be enough to just
> > tick the "needs review" checkbox.
>
> Yes, we agree here. But still the radio button should move to the empty input
> field, I think. This way it is clear that the current translation will change
> - to nothing.
> There is this big misunderstanding that you and others had about the meaning
> of "Someone should review..." because it was always only intended for newly
> entered translations. The use that you added now is a new feature, not a fix
> of a buggy feature.
> That is why I think moving the radio button down to the input field will keep
> the original intention that the checkbox relates to the input box, not the
> current translation, while adding the extra feature of resetting the current
> translatoin.
Yes, this is a new functionality and maybe I should open a new bug for this branch.

While we don't have the „is_reviewed” field in the DB and the +translate page is not redesigned, this can be a workaround for this bug.

> I know this requires some javascript. I don't know how comfortable you are
> with writing that. If you want to you can file an extra bug for it which
> should be fixed this cycle. You can land this branch as it is (see above
> suggestions) but it would be great if that extra javascript would be in it.
> ;-)
I have added the required javascript.

I don't know how to include the windmill test since it depends on this branch https://code.edge.launchpad.net/~adiroiban/launchpad/bug-540105/+merge/21552.

Maybe I will have to wait for the landing of that branch and then merge the windmill test for this branch and do a bit of refactoring for the initilization of Javascript code on this page.
Here is the latest diff
=== modified file 'lib/canonical/launchpad/javascript/translations/pofile.js'
--- lib/canonical/launchpad/javascript/translations/pofile.js 2010-02-25 12:56:45 +0000
+++ lib/canonical/launchpad/javascript/translations/pofile.js 2010-03-22 17:17:22 +0000
@@ -321,12 +321,41 @@
                 singular_copy_text);
         }
     }
-}
+};

+var initializeResetBehavior = function (fields) {
+ for (var key = 0; key < fields.length; key++) {
+ var html_parts ...

Read more...

Revision history for this message
Adi Roiban (adiroiban) wrote :
Download full text (13.1 KiB)

Here are the Windmill tests and the changes in the pofile and translation +message javascript initialization.

=== modified file 'lib/lp/translations/windmill/tests/test_pofile_translate.py'
--- lib/lp/translations/windmill/tests/test_pofile_translate.py 2010-03-17 17:33:04 +0000
+++ lib/lp/translations/windmill/tests/test_pofile_translate.py 2010-03-22 23:02:19 +0000
@@ -50,3 +50,132 @@

         # Check that the associated radio button is selected.
         client.asserts.assertChecked(id=radiobutton_id)
+
+ def _check_reset_translation_select(
+ self, client, checkbox, singular_select, singular_current,
+ plural_select=None):
+ """Checks that the new translation select radio buttons are checked
+ when ticking 'Someone should review this translation' checkbox.
+ """
+
+ client.waits.forElement(
+ id=checkbox, timeout=constants.FOR_ELEMENT)
+ client.waits.forElement(
+ id=singular_select, timeout=constants.FOR_ELEMENT)
+ if plural_select is not None:
+ client.waits.forElement(
+ id=plural_select, timeout=constants.FOR_ELEMENT)
+
+ # Check that initialy the checkbox is not checked and
+ # that the radio buttons are not selected.
+ client.asserts.assertNotChecked(id=checkbox)
+ client.asserts.assertNotChecked(id=singular_select)
+ client.asserts.assertChecked(id=singular_current)
+ if plural_select is not None:
+ client.asserts.assertNotChecked(id=plural_select)
+
+ # Check the checkbox
+ client.click(id=checkbox)
+
+ # Check that the checkbox and the new translation radio buttons are
+ # selected.
+ client.asserts.assertChecked(id=checkbox)
+ client.asserts.assertChecked(id=singular_select)
+ client.asserts.assertNotChecked(id=singular_current)
+ if plural_select is not None:
+ client.asserts.assertChecked(id=plural_select)
+
+ # We select the current translation for the singular form.
+ client.click(id=singular_current)
+
+ # Then then we uncheck the 'Someone needs to review this translation'
+ # checkbox.
+ client.click(id=checkbox)
+
+ # Unchecking the 'Someone needs to review this translation' checkbox
+ # will not change the state of the radio buttons.
+ client.asserts.assertNotChecked(id=checkbox)
+ client.asserts.assertNotChecked(id=singular_select)
+ client.asserts.assertChecked(id=singular_current)
+ if plural_select is not None:
+ client.asserts.assertChecked(id=plural_select)
+
+ def test_pofile_reset_translation_select(self):
+ """Test for automatically selecting new translation when
+ 'Someone needs to review this translations' is checked.
+ """
+ client = self.client
+ user = lpuser.TRANSLATIONS_ADMIN
+
+ # Go to the zoom in page for a translation with plural forms.
+ self.client.open(
+ url='http://translations.launchpad.dev:8085/'
+ 'ubuntu/hoary/+source/evolution/+pots/'
+ 'evolution-2.2/es/15/+transl...

Revision history for this message
Henning Eggers (henninge) wrote :
Download full text (4.0 KiB)

m 23.03.2010 01:31, schrieb Adi Roiban:
> Here are the Windmill tests and the changes in the pofile and translation +message javascript initialization.

Thanks Adi, looks very good to me. I just have one request.

>
> === modified file 'lib/lp/translations/windmill/tests/test_pofile_translate.py'
> --- lib/lp/translations/windmill/tests/test_pofile_translate.py 2010-03-17 17:33:04 +0000
> +++ lib/lp/translations/windmill/tests/test_pofile_translate.py 2010-03-22 23:02:19 +0000
> @@ -50,3 +50,132 @@
>
> # Check that the associated radio button is selected.
> client.asserts.assertChecked(id=radiobutton_id)
> +
> + def _check_reset_translation_select(
[...]
> + # Then then we uncheck the 'Someone needs to review this translation'
> + # checkbox.
> + client.click(id=checkbox)
> +
> + # Unchecking the 'Someone needs to review this translation' checkbox
> + # will not change the state of the radio buttons.

I think that this will lead to accidental unsetting of translations. Someone going through translations and clicking on "Someone should ..." but then changes his mind will most likely forget to move the radio button back to preserve the current translation. The radio button should jump back to the current translation if the text field is empty (only then!).

See the code below.

> + client.asserts.assertNotChecked(id=checkbox)
> + client.asserts.assertNotChecked(id=singular_select)
> + client.asserts.assertChecked(id=singular_current)
> + if plural_select is not None:
> + client.asserts.assertChecked(id=plural_select)
> +
> + def test_pofile_reset_translation_select(self):
[...]

> === modified file 'lib/lp/translations/templates/pofile-translate.pt'
> === modified file 'lib/lp/translations/templates/translationmessage-translate.pt'

Thanks for moving the code out of the templates!

> === modified file 'lib/canonical/launchpad/javascript/translations/pofile.js'
[...]

> +
> +var initializeResetBehavior = function (fields) {
> + for (var key = 0; key< fields.length; key++) {
> + var html_parts = fields[key].split('_');
> + var msgset_id = html_parts[0] + '_' + html_parts[1];
> + var node = Y.one('#' + msgset_id + '_force_suggestion');
> + if (node === null) {
> + // If we don't have a force_suggestion checkbox associated with
> + // this field, just continue to the next field.
> + break;
> + }
> + Y.on(
> + 'click',
> + function (e, translation_id) {
> + if (this === null) {
> + // Don't do nothing if we don't have a context object.
> + return;
> + }
> + if (this.get('checked') == true) {
> + var select = Y.one('#' + translation_id + '_select');
> + if (select !== null) {
> + select.set('checked', true);
> + }

I was thinking about how this would be done and came up with this code. Feel free to use it or find your own solution ... ;-)

               } else {
                   var inputf...

Read more...

review: Approve (code)
Revision history for this message
Adi Roiban (adiroiban) wrote :
Download full text (9.2 KiB)

Hi Henning,

I'm back in business.

Here are the changes after applying your JS code and updating the windmill tests.

I have also merged with devel.

If everything is ok, can you please send this branch to ec2-test?

Thanks!
=== modified file 'lib/canonical/launchpad/javascript/translations/pofile.js'
--- lib/canonical/launchpad/javascript/translations/pofile.js 2010-03-23 00:22:40 +0000
+++ lib/canonical/launchpad/javascript/translations/pofile.js 2010-04-11 23:07:06 +0000
@@ -374,10 +374,25 @@
                     return;
                 }
                 if (this.get('checked') == true) {
+ // When checking the "Need review" checkbox the new
+ // translation should be automatically checked.
                     var select = Y.one('#' + translation_id + '_select');
                     if (select !== null) {
                         select.set('checked', true);
                     }
+ } else {
+ // When unchecking the "Need review" checkbox and the new
+ // translation is empty, the current translation should be
+ // automatically selected.
+ var inputfield = Y.one('#' + translation_id);
+ if (inputfield !== null && inputfield.get('value') == '') {
+ var current_radio_id = translation_id.replace(
+ /_new$/, '_radiobutton');
+ var current_radio = Y.one('#' + current_radio_id);
+ if (current_radio !== null) {
+ current_radio.set('checked', true);
+ }
+ }
                 }
             },
             node , node, fields[key]

=== modified file 'lib/lp/translations/windmill/tests/test_pofile_translate.py'
--- lib/lp/translations/windmill/tests/test_pofile_translate.py 2010-03-23 00:22:40 +0000
+++ lib/lp/translations/windmill/tests/test_pofile_translate.py 2010-04-11 23:40:51 +0000
@@ -52,8 +52,8 @@
         client.asserts.assertChecked(id=radiobutton_id)

     def _check_reset_translation_select(
- self, client, checkbox, singular_select, singular_current,
- plural_select=None):
+ self, client, checkbox, singular_new_select, singular_current_select,
+ singular_new_field=None, plural_new_select=None):
         """Checks that the new translation select radio buttons are checked
         when ticking 'Someone should review this translation' checkbox.
         """
@@ -61,44 +61,61 @@
         client.waits.forElement(
             id=checkbox, timeout=constants.FOR_ELEMENT)
         client.waits.forElement(
- id=singular_select, timeout=constants.FOR_ELEMENT)
- if plural_select is not None:
- client.waits.forElement(
- id=plural_select, timeout=constants.FOR_ELEMENT)
+ id=singular_new_select, timeout=constants.FOR_ELEMENT)
+ client.waits.forElement(
+ id=singular_current_select, timeout=constants.FOR_ELEMENT)
+ if plural_new_select is not None:
+ client.waits.forElement(
+ id=plural_new_select, timeou...

Read more...

Revision history for this message
Henning Eggers (henninge) wrote :

Am 12.04.2010 02:39, schrieb Adi Roiban:
> I'm back in business.

Good to hear! ;-)

>
> Here are the changes after applying your JS code and updating the windmill tests.

Thanks for updating those tests. I actually forgot to try my code on plural
forms but I am glad to see that works, too. ;-)

>
> I have also merged with devel.
>
> If everything is ok, can you please send this branch to ec2-test?

I will but I have two little commenting and naming complaints that you should
please take care of first.

>
> Thanks!

You are most welcome! ;-)

Henning

> === modified file 'lib/canonical/launchpad/javascript/translations/pofile.js'
> --- lib/canonical/launchpad/javascript/translations/pofile.js 2010-03-23 00:22:40 +0000
> +++ lib/canonical/launchpad/javascript/translations/pofile.js 2010-04-11 23:07:06 +0000
> @@ -374,10 +374,25 @@
> return;
> }
> if (this.get('checked') == true) {
> + // When checking the "Need review" checkbox the new
> + // translation should be automatically checked.

Please don't use "should" in comments, this is not a test. ;-)

Also, it is not necessary to explain what the code does unless the code is
really complicated or some special condition/work-around/bug etc has to be
known to understand the code.

Generally, the code should speak for itself. If you feel the code does not,
maybe a variable needs a more descriptive name?

> var select = Y.one('#' + translation_id + '_select');
> if (select !== null) {
> select.set('checked', true);
> }
> + } else {
> + // When unchecking the "Need review" checkbox and the new
> + // translation is empty, the current translation should be
> + // automatically selected.

Same here.

> + var inputfield = Y.one('#' + translation_id);
> + if (inputfield !== null&& inputfield.get('value') == '') {
> + var current_radio_id = translation_id.replace(
> + /_new$/, '_radiobutton');
> + var current_radio = Y.one('#' + current_radio_id);
> + if (current_radio !== null) {
> + current_radio.set('checked', true);
> + }
> + }
> }
> },
> node , node, fields[key]
>
> === modified file 'lib/lp/translations/windmill/tests/test_pofile_translate.py'
> --- lib/lp/translations/windmill/tests/test_pofile_translate.py 2010-03-23 00:22:40 +0000
> +++ lib/lp/translations/windmill/tests/test_pofile_translate.py 2010-04-11 23:40:51 +0000
> @@ -52,8 +52,8 @@
> client.asserts.assertChecked(id=radiobutton_id)
>
> def _check_reset_translation_select(

Sorry, I did not notice this before. Methods are always camelCase, functions
and variables use_underscores. It's in the style guide ... ;-)

       def _checkResetTranslationSelect(

That's all.

Revision history for this message
Adi Roiban (adiroiban) wrote :
Download full text (6.0 KiB)

I have renamed the method.

I have also added a test for pt_BR, since the use of „_” in language code and as a separator for form fields can generated errors.

I have removed „user.ensure_login(self.client)” and leave it only of the first page loaded in this test.
Do we really need to call it after each page load?

I have renamed the variables and remove the comment. Many thanks for this review!

=== modified file 'lib/canonical/launchpad/javascript/translations/pofile.js'
--- lib/canonical/launchpad/javascript/translations/pofile.js 2010-04-12 00:39:34 +0000
+++ lib/canonical/launchpad/javascript/translations/pofile.js 2010-04-12 14:49:09 +0000
@@ -374,23 +374,20 @@
                     return;
                 }
                 if (this.get('checked') == true) {
- // When checking the "Need review" checkbox the new
- // translation should be automatically checked.
- var select = Y.one('#' + translation_id + '_select');
- if (select !== null) {
- select.set('checked', true);
+ var new_translation_select = Y.one(
+ '#' + translation_id + '_select');
+ if (new_translation_select !== null) {
+ new_translation_select.set('checked', true);
                     }
                 } else {
- // When unchecking the "Need review" checkbox and the new
- // translation is empty, the current translation should be
- // automatically selected.
- var inputfield = Y.one('#' + translation_id);
- if (inputfield !== null && inputfield.get('value') == '') {
- var current_radio_id = translation_id.replace(
+ var new_translation_field = Y.one('#' + translation_id);
+ if (new_translation_field !== null &&
+ new_translation_field.get('value') == '') {
+ var current_select_id = translation_id.replace(
                            /_new$/, '_radiobutton');
- var current_radio = Y.one('#' + current_radio_id);
- if (current_radio !== null) {
- current_radio.set('checked', true);
+ var current_select = Y.one('#' + current_select_id);
+ if (current_select !== null) {
+ current_select.set('checked', true);
                        }
                     }
                 }

=== modified file 'lib/lp/translations/windmill/tests/test_pofile_translate.py'
--- lib/lp/translations/windmill/tests/test_pofile_translate.py 2010-04-12 00:39:34 +0000
+++ lib/lp/translations/windmill/tests/test_pofile_translate.py 2010-04-12 14:37:32 +0000
@@ -51,7 +51,7 @@
         # Check that the associated radio button is selected.
         client.asserts.assertChecked(id=radiobutton_id)

- def _check_reset_translation_select(
+ def _checkResetTranslationSelect(
         self, client, checkbox, singular_new_select, singular_current_select,
         singular_new_field=None, p...

Read more...

Revision history for this message
Henning Eggers (henninge) wrote :

Am 12.04.2010 16:57, schrieb Adi Roiban:
> I have renamed the method.

Thanks.

>
> I have also added a test for pt_BR, since the use of „_” in language code and as a separator for form fields can generated errors.

Good thinking! Very good, indeed.

>
> I have removed „user.ensure_login(self.client)” and leave it only of the first page loaded in this test.
> Do we really need to call it after each page load?

I don't think so, the browser stays logged in, doesn't it?

>
> I have renamed the variables and remove the comment. Many thanks for this review!

Thanks a lot for your work, it is much appreicated! ;-)

I will land this now.

Henning

Revision history for this message
Adi Roiban (adiroiban) wrote :

I forget to update the previous integration test with the new implementation that actually requires selecting the new empty translation.

I have fixed it.

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-needs-review-flags-preserved.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-translate-needs-review-flags-preserved.txt 2010-03-18 20:42:37 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-translate-needs-review-flags-preserved.txt 2010-04-12 19:31:17 +0000
@@ -75,8 +75,9 @@
     ... 'evolution/+pots/man/es/1/+translate')
     >>> inputradio = admin_browser.getControl(
     ... name='msgset_166_es_translation_0_radiobutton')
- >>> inputradio.value = [ 'msgset_166_es_translation_0_new']
- >>> inputfield = admin_browser.getControl(name='msgset_166_es_translation_0_new')
+ >>> inputradio.value = ['msgset_166_es_translation_0_new']
+ >>> inputfield = admin_browser.getControl(
+ ... name='msgset_166_es_translation_0_new')
     >>> inputfield.value = 'New test translation'
     >>> admin_browser.getControl('Save & Continue').click()
     >>> print extract_text(find_tag_by_id(admin_browser.contents,
@@ -88,11 +89,15 @@
     Located in ...

 'Someone should review this translation' can be used to reset the translation,
-and after clicking 'Save & Continue', all translations entered for this
+when a new emtpy translation is added and marked as needing review.
+After clicking 'Save & Continue', all translations entered for this
 message will be listed as suggestions.

     >>> admin_browser.getControl(
     ... 'Someone should review this translation').selected = True
+ >>> inputradio = admin_browser.getControl(
+ ... name='msgset_166_es_translation_0_radiobutton')
+ >>> inputradio.value = ['msgset_166_es_translation_0_new']
     >>> admin_browser.getControl('Save & Continue').click()
     >>> print extract_text(find_tag_by_id(admin_browser.contents,
     ... 'messages_to_translate'))

Revision history for this message
Henning Eggers (henninge) wrote :

I tried to land it but it has another merge conflict. Please merge the current
devel again and I will try to land it again.

Revision history for this message
Adi Roiban (adiroiban) wrote :

Hi,

I merged and solved the conflict and push the changes.

Cheers,
Adi

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/javascript/translations/pofile.js'
--- lib/canonical/launchpad/javascript/translations/pofile.js 2010-03-26 23:26:18 +0000
+++ lib/canonical/launchpad/javascript/translations/pofile.js 2010-04-13 13:02:29 +0000
@@ -13,7 +13,7 @@
13 * Function to disable/enable all suggestions as they are marked/unmarked13 * Function to disable/enable all suggestions as they are marked/unmarked
14 * for dismission.14 * for dismission.
15 */15 */
16self.setupSuggestionDismissal = function(e) {16var setupSuggestionDismissal = function(e) {
17 all_dismiss_boxes = Y.all('.dismiss_action');17 all_dismiss_boxes = Y.all('.dismiss_action');
18 if (all_dismiss_boxes !== null) {18 if (all_dismiss_boxes !== null) {
19 all_dismiss_boxes.each(function(checkbox) {19 all_dismiss_boxes.each(function(checkbox) {
@@ -57,7 +57,8 @@
57 hide_anim.run();57 hide_anim.run();
58};58};
5959
60self.updateNotificationBox = function(e) {60
61var updateNotificationBox = function(e) {
61 var notice = Y.one('.important-notice-container');62 var notice = Y.one('.important-notice-container');
62 if (notice === null) {63 if (notice === null) {
63 // We have no notice container on this page, this is why there is64 // We have no notice container on this page, this is why there is
@@ -92,7 +93,7 @@
92};93};
9394
9495
95self.setFocus = function(field) {96var setFocus = function(field) {
96 // if there is nofield, do nothing97 // if there is nofield, do nothing
97 if (Y.one('#' + field) !== null) {98 if (Y.one('#' + field) !== null) {
98 Y.one('#' + field).focus();99 Y.one('#' + field).focus();
@@ -101,7 +102,7 @@
101102
102103
103var setNextFocus = function(e, field) {104var setNextFocus = function(e, field) {
104 self.setFocus(field);105 setFocus(field);
105 // stopPropagation() and preventDefault()106 // stopPropagation() and preventDefault()
106 e.halt();107 e.halt();
107};108};
@@ -111,8 +112,8 @@
111112
112 // Original singular test is focused first to make sure113 // Original singular test is focused first to make sure
113 // it is visible when scrolling up114 // it is visible when scrolling up
114 self.setFocus(original);115 setFocus(original);
115 self.setFocus(field);116 setFocus(field);
116 // stopPropagation() and preventDefault()117 // stopPropagation() and preventDefault()
117 e.halt();118 e.halt();
118};119};
@@ -180,7 +181,7 @@
180};181};
181182
182183
183var selectTranslation = function(e, widget_id) {184var selectTranslation = function(e, field) {
184 // Don't select when tabbing, navigating and simply pressing185 // Don't select when tabbing, navigating and simply pressing
185 // enter to submit the form.186 // enter to submit the form.
186 // Looks like this is not needed for Epiphany and Chromium187 // Looks like this is not needed for Epiphany and Chromium
@@ -190,7 +191,8 @@
190 (e.shiftKey && e.altKey && e.keyCode == 75)) {191 (e.shiftKey && e.altKey && e.keyCode == 75)) {
191 return;192 return;
192 }193 }
193 selectWidgetByID(widget_id);194 var translation_select_id = field + '_select';
195 selectWidgetByID(translation_select_id);
194};196};
195197
196198
@@ -204,11 +206,11 @@
204 }206 }
205 // Shift+Alt+f - Go to search field207 // Shift+Alt+f - Go to search field
206 if (e.shiftKey && e.altKey && e.keyCode == 70) {208 if (e.shiftKey && e.altKey && e.keyCode == 70) {
207 self.setFocus('search_box');209 setFocus('search_box');
208 }210 }
209 // Shift+Alt+b - Go to first translation field211 // Shift+Alt+b - Go to first translation field
210 if (e.shiftKey && e.altKey && e.keyCode == 66) {212 if (e.shiftKey && e.altKey && e.keyCode == 66) {
211 self.setFocus(fields[0]);213 setFocus(fields[0]);
212 }214 }
213 // Shift+Alt+n - Go to next page in batch215 // Shift+Alt+n - Go to next page in batch
214 if (e.shiftKey && e.altKey && e.keyCode == 78) {216 if (e.shiftKey && e.altKey && e.keyCode == 78) {
@@ -278,14 +280,13 @@
278 var html_parts = fields[key].split('_');280 var html_parts = fields[key].split('_');
279 var msgset_id = html_parts[0] + '_' + html_parts[1];281 var msgset_id = html_parts[0] + '_' + html_parts[1];
280 var translation_stem = fields[key].replace(/_translation_(\d)+_new/,"");282 var translation_stem = fields[key].replace(/_translation_(\d)+_new/,"");
281 var translation_select_id = fields[key] + '_select';
282283
283 Y.on(284 Y.on(
284 'change', selectTranslation,285 'change', selectTranslation,
285 '#' + fields[key], Y, translation_select_id);286 '#' + fields[key], Y, fields[key]);
286 Y.on(287 Y.on(
287 'keypress', selectTranslation,288 'keypress', selectTranslation,
288 '#' + fields[key], Y, translation_select_id);289 '#' + fields[key], Y, fields[key]);
289290
290 // Set next field and copy text for all but last field291 // Set next field and copy text for all but last field
291 // (last is Save & Continue button)292 // (last is Save & Continue button)
@@ -346,11 +347,7 @@
346};347};
347348
348349
349/**350var initializeKeyBindings = function(e) {
350 * Initialize event-key bindings such as moving to the next or previous
351 * field, or copying original text
352 */
353self.initializeKeyBindings = function(e) {
354351
355 if (translations_order.length < 1) {352 if (translations_order.length < 1) {
356 // If no translations fiels are displayed on the page353 // If no translations fiels are displayed on the page
@@ -366,5 +363,109 @@
366 initializeFieldsKeyBindings(fields); 363 initializeFieldsKeyBindings(fields);
367};364};
368365
366
367var initializeResetBehavior = function (fields) {
368 for (var key = 0; key < fields.length; key++) {
369 var html_parts = fields[key].split('_');
370 var msgset_id = html_parts[0] + '_' + html_parts[1];
371 var node = Y.one('#' + msgset_id + '_force_suggestion');
372 if (node === null) {
373 // If we don't have a force_suggestion checkbox associated with
374 // this field, just continue to the next field.
375 break;
376 }
377 Y.on(
378 'click',
379 function (e, translation_id) {
380 if (this === null) {
381 // Don't do nothing if we don't have a context object.
382 return;
383 }
384 if (this.get('checked') == true) {
385 var new_translation_select = Y.one(
386 '#' + translation_id + '_select');
387 if (new_translation_select !== null) {
388 new_translation_select.set('checked', true);
389 }
390 } else {
391 var new_translation_field = Y.one('#' + translation_id);
392 if (new_translation_field !== null &&
393 new_translation_field.get('value') == '') {
394 var current_select_id = translation_id.replace(
395 /_new$/, '_radiobutton');
396 var current_select = Y.one('#' + current_select_id);
397 if (current_select !== null) {
398 current_select.set('checked', true);
399 }
400 }
401 }
402 },
403 node , node, fields[key]
404 );
405 }
406};
407
408/**
409 * Initialize common Javascript code for POFile and TranslationMessage
410 * +translate pages.
411 *
412 * This will add event-key bindings such as moving to the next or previous
413 * field, or copying original text.
414 * It will also initializing the reset checkbox behavior and will show the
415 * error notifications.
416 */
417
418var initializeBaseTranslate = function () {
419 try {
420 setupSuggestionDismissal();
421 } catch (e) {
422 Y.log(e, "error");
423 }
424
425 try {
426 initializeKeyBindings();
427 } catch (e) {
428 Y.log(e, "error");
429 }
430
431 try {
432 var fields = translations_order.split(' ');
433 initializeResetBehavior(fields);
434 } catch (e) {
435 Y.log(e, "error");
436 }
437
438 try {
439 setFocus(autofocus_field);
440 } catch (e) {
441 Y.log(e, "error");
442 }
443}
444
445/**
446 * Initialize Javascript code for a POFile +translate page.
447 *
448 * This will initialize the base code and will also show the guidelines
449 * if needeed.
450 */
451self.initializePOFile = function(e) {
452 try {
453 updateNotificationBox();
454 } catch (e) {
455 Y.log(e, "error");
456 }
457 initializeBaseTranslate();
458};
459
460/**
461 * Initialize Javascript code for a TranslationMessage +translate page.
462 *
463 * This will initialize the base code.
464 */
465self.initializeTranslationMessage = function(e) {
466 initializeBaseTranslate();
467};
468
469
369}, "0.1", {"requires": ["event", "event-key", "node", "cookie", "anim"]});470}, "0.1", {"requires": ["event", "event-key", "node", "cookie", "anim"]});
370471
371472
=== modified file 'lib/lp/translations/browser/tests/translationmessage-views.txt'
--- lib/lp/translations/browser/tests/translationmessage-views.txt 2009-09-11 22:24:51 +0000
+++ lib/lp/translations/browser/tests/translationmessage-views.txt 2010-04-13 13:02:29 +0000
@@ -689,3 +689,101 @@
689 False689 False
690 >>> subview.shared_translationmessage == translationmessage690 >>> subview.shared_translationmessage == translationmessage
691 True691 True
692
693
694Resetting current translation
695-----------------------------
696
697The current translation can be reset by submitting an empty translation
698or a translation similar to the current one while forcing suggestions.
699
700We need to force timestamp update, since suggestions are submitted too fast.
701
702 >>> from lp.translations.interfaces.translationgroup import (
703 ... TranslationPermission)
704 >>> pofile = factory.makePOFile('es')
705 >>> potemplate = pofile.potemplate
706 >>> potmsgset = factory.makePOTMsgSet(potemplate, sequence=1)
707 >>> translationmessage = factory.makeTranslationMessage(
708 ... potmsgset=potmsgset, pofile=pofile,
709 ... translations = [u"A shared translation"])
710 >>> translationmessage.setPOFile(pofile)
711 >>> product = potemplate.productseries.product
712 >>> product.translationpermission = TranslationPermission.CLOSED
713 >>> year_tick = 2100
714 >>> def submit_translation(
715 ... translationmessage, translation, force_suggestion=False):
716 ... global year_tick
717 ... pofile = translationmessage.pofile
718 ... potmsgset = translationmessage.potmsgset
719 ... server_url = '/'.join(
720 ... [canonical_url(translationmessage), '+translate'])
721 ... now = (str(year_tick) +
722 ... datetime.now(UTC).strftime('-%m-%dT%H:%M:%S+00:00'))
723 ... year_tick += 1
724 ... msgset_id = 'msgset_' + str(potmsgset.id)
725 ... msgset_id_lang = msgset_id + '_' + pofile.language.code
726 ... form = {
727 ... 'lock_timestamp': now,
728 ... 'alt': None,
729 ... msgset_id : None,
730 ... msgset_id_lang + '_translation_0_radiobutton':
731 ... msgset_id_lang + '_translation_0_new',
732 ... msgset_id_lang + '_translation_0_new': translation,
733 ... 'submit_translations': 'Save &amp; Continue'}
734 ... if force_suggestion:
735 ... form[msgset_id_lang + '_needsreview'] = 'force_suggestion'
736 ... tm_view = create_view(
737 ... translationmessage, "+translate", form=form,
738 ... layer=TranslationsLayer, server_url=server_url)
739 ... tm_view.request.method = 'POST'
740 ... tm_view.initialize()
741
742 >>> def print_current_translation(potmsgset, pofile):
743 ... current_translation = potmsgset.getCurrentTranslationMessage(
744 ... pofile.potemplate, pofile.language)
745 ... if current_translation is not None:
746 ... print current_translation.translations
747
748 >>> def print_local_suggestions(potmsgset, pofile):
749 ... import operator
750 ... local = sorted(
751 ... potmsgset.getLocalTranslationMessages(
752 ... pofile.potemplate, pofile.language),
753 ... key=operator.attrgetter("date_created"),
754 ... reverse=True)
755 ... for suggestion in local:
756 ... print suggestion.translations
757
758We will make an initial translation.
759
760 >>> submit_translation(
761 ... translationmessage, u'Foo')
762 >>> print_local_suggestions(potmsgset, pofile)
763 >>> print_current_translation(potmsgset, pofile)
764 [u'Foo']
765
766Forcing suggestions while submitting an empty translation will reset the
767translation for this message and all previously entered translations will be
768listed as suggestions.
769
770 >>> submit_translation(translationmessage, u'', force_suggestion=True)
771 >>> print_local_suggestions(potmsgset, pofile)
772 [u'Foo']
773 [u'A shared translation']
774 >>> print_current_translation(potmsgset, pofile)
775
776Only users with edit rights are allowed to reset a translation.
777
778 >>> submit_translation(translationmessage, u'New current translation')
779 >>> print_current_translation(potmsgset, pofile)
780 [u'New current translation']
781 >>> print_local_suggestions(potmsgset, pofile)
782 >>> login('no-priv@canonical.com')
783 >>> submit_translation(
784 ... translationmessage, u'New current translation',
785 ... force_suggestion=True)
786 >>> login('carlos@canonical.com')
787 >>> print_local_suggestions(potmsgset, pofile)
788 >>> print_current_translation(potmsgset, pofile)
789 [u'New current translation']
692790
=== modified file 'lib/lp/translations/browser/translationmessage.py'
--- lib/lp/translations/browser/translationmessage.py 2010-04-07 06:23:30 +0000
+++ lib/lp/translations/browser/translationmessage.py 2010-04-13 13:02:29 +0000
@@ -425,6 +425,16 @@
425 is_imported=False, lock_timestamp=self.lock_timestamp,425 is_imported=False, lock_timestamp=self.lock_timestamp,
426 force_suggestion=force_suggestion,426 force_suggestion=force_suggestion,
427 force_diverged=force_diverge)427 force_diverged=force_diverge)
428
429 # If suggestions were forced and user has the rights to do it,
430 # reset the current translation.
431 empty_suggestions = self._areSuggestionsEmpty(translations)
432 if (force_suggestion and
433 self.user_is_official_translator and
434 empty_suggestions):
435 potmsgset.resetCurrentTranslation(
436 self.pofile, self.lock_timestamp)
437
428 except TranslationConflict:438 except TranslationConflict:
429 return (439 return (
430 u'Somebody else changed this translation since you started.'440 u'Somebody else changed this translation since you started.'
@@ -439,6 +449,13 @@
439 self._observeTranslationUpdate(potmsgset)449 self._observeTranslationUpdate(potmsgset)
440 return None450 return None
441451
452 def _areSuggestionsEmpty(self, suggestions):
453 """Return true if all suggestions are empty strings or None."""
454 for index in suggestions:
455 if (suggestions[index] is not None and suggestions[index] != ""):
456 return False
457 return True
458
442 def _prepareView(self, view_class, current_translation_message, error):459 def _prepareView(self, view_class, current_translation_message, error):
443 """Collect data and build a TranslationMessageView for display."""460 """Collect data and build a TranslationMessageView for display."""
444 # XXX: kiko 2006-09-27:461 # XXX: kiko 2006-09-27:
445462
=== modified file 'lib/lp/translations/interfaces/potmsgset.py'
--- lib/lp/translations/interfaces/potmsgset.py 2010-03-05 15:31:22 +0000
+++ lib/lp/translations/interfaces/potmsgset.py 2010-04-13 13:02:29 +0000
@@ -250,6 +250,18 @@
250 If a translation conflict is detected, TranslationConflict is raised.250 If a translation conflict is detected, TranslationConflict is raised.
251 """251 """
252252
253 def resetCurrentTranslation(pofile, lock_timestamp):
254 """Reset the currently used translation.
255
256 This will set the "is_current" attribute to False and if the message
257 is diverge, will try to converge it.
258 :param pofile: a `POFile` to dismiss suggestions from.
259 :param lock_timestamp: the timestamp when we checked the values we
260 want to update.
261
262 If a translation conflict is detected, TranslationConflict is raised.
263 """
264
253 def applySanityFixes(unicode_text):265 def applySanityFixes(unicode_text):
254 """Return 'unicode_text' or None after doing some sanitization.266 """Return 'unicode_text' or None after doing some sanitization.
255267
256268
=== modified file 'lib/lp/translations/model/potmsgset.py'
--- lib/lp/translations/model/potmsgset.py 2010-03-05 15:31:22 +0000
+++ lib/lp/translations/model/potmsgset.py 2010-04-13 13:02:29 +0000
@@ -883,6 +883,24 @@
883 matching_message.sync()883 matching_message.sync()
884 return matching_message884 return matching_message
885885
886 def _maybeRaiseTranslationConflict(self, message, lock_timestamp):
887 """Checks if there is a translation conflict for the message.
888
889 If a translation conflict is detected, TranslationConflict is raised.
890 """
891 if message.date_reviewed is not None:
892 use_date = message.date_reviewed
893 else:
894 use_date = message.date_created
895 if use_date >= lock_timestamp:
896 raise TranslationConflict(
897 'While you were reviewing these suggestions, somebody '
898 'else changed the actual translation. This is not an '
899 'error but you might want to re-review the strings '
900 'concerned.')
901 else:
902 return
903
886 def dismissAllSuggestions(self, pofile, reviewer, lock_timestamp):904 def dismissAllSuggestions(self, pofile, reviewer, lock_timestamp):
887 """See `IPOTMsgSet`."""905 """See `IPOTMsgSet`."""
888 assert(lock_timestamp is not None)906 assert(lock_timestamp is not None)
@@ -893,19 +911,29 @@
893 current = self.updateTranslation(911 current = self.updateTranslation(
894 pofile, reviewer, [], False, lock_timestamp)912 pofile, reviewer, [], False, lock_timestamp)
895 else:913 else:
896 if current.date_reviewed is not None:914 # Check for translation conflicts and update review fields.
897 use_date = current.date_reviewed915 self._maybeRaiseTranslationConflict(current, lock_timestamp)
898 else:916 current.reviewer = reviewer
899 use_date = current.date_created917 current.date_reviewed = lock_timestamp
900 if use_date >= lock_timestamp:918
901 raise TranslationConflict(919 def resetCurrentTranslation(self, pofile, lock_timestamp):
902 'While you were reviewing these suggestions, somebody '920 """See `IPOTMsgSet`."""
903 'else changed the actual translation. This is not an '921
904 'error but you might want to re-review the strings '922 assert(lock_timestamp is not None)
905 'concerned.')923
906 else:924 current = self.getCurrentTranslationMessage(
907 current.reviewer = reviewer925 pofile.potemplate, pofile.language)
908 current.date_reviewed = lock_timestamp926
927 if (current is not None):
928 # Check for transltion conflicts and update the required
929 # attributes.
930 self._maybeRaiseTranslationConflict(current, lock_timestamp)
931 current.is_current = False
932 # Converge the current translation only if it is diverged and not
933 # imported.
934 if current.potemplate is not None and not current.is_imported:
935 current.potemplate = None
936 pofile.date_changed = UTC_NOW
909937
910 def applySanityFixes(self, text):938 def applySanityFixes(self, text):
911 """See `IPOTMsgSet`."""939 """See `IPOTMsgSet`."""
912940
=== modified file 'lib/lp/translations/model/translationmessage.py'
--- lib/lp/translations/model/translationmessage.py 2009-10-28 13:08:05 +0000
+++ lib/lp/translations/model/translationmessage.py 2010-04-13 13:02:29 +0000
@@ -478,12 +478,12 @@
478 implements(ITranslationMessageSet)478 implements(ITranslationMessageSet)
479479
480 def getByID(self, ID):480 def getByID(self, ID):
481 """See `ILanguageSet`."""481 """See `ITranslationMessageSet`."""
482 try:482 try:
483 return TranslationMessage.get(ID)483 return TranslationMessage.get(ID)
484 except SQLObjectNotFound:484 except SQLObjectNotFound:
485 return None485 return None
486486
487 def selectDirect(self, where=None, order_by=None):487 def selectDirect(self, where=None, order_by=None):
488 """See `ILanguageSet`."""488 """See `ITranslationMessageSet`."""
489 return TranslationMessage.select(where, orderBy=order_by)489 return TranslationMessage.select(where, orderBy=order_by)
490490
=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-needs-review-flags-preserved.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-translate-needs-review-flags-preserved.txt 2009-07-01 20:45:39 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-translate-needs-review-flags-preserved.txt 2010-04-13 13:02:29 +0000
@@ -1,6 +1,10 @@
1= Forcing suggestions =1Someone should review this translation usage
22============================================
3When translation is restricted to a particular set of users3
4Forcing suggestions
5-------------------
6
7When translating is restricted to a particular set of users
4(like Evolution product is restricted for Spanish), other users8(like Evolution product is restricted for Spanish), other users
5don't see a checkbox named 'Someone should review this translation'.9don't see a checkbox named 'Someone should review this translation'.
610
@@ -53,3 +57,58 @@
53 >>> print extract_text(find_tag_by_id(user_browser.contents,57 >>> print extract_text(find_tag_by_id(user_browser.contents,
54 ... 'msgset_130_es_suggestion_701_0'))58 ... 'msgset_130_es_suggestion_701_0'))
55 New suggestion59 New suggestion
60
61
62Reseting translations
63---------------------
64
65If the 'Someone should review this translation' checkbox is used without
66adding any new suggestions or adding an empty string, the current translation
67will be reset causing all suggestions entered for this message to be listed
68again.
69
70A new translation is entered and checked that it was saved as the current
71translation, while no suggestions are displayed.
72
73 >>> admin_browser.open(
74 ... 'http://translations.launchpad.dev/ubuntu/hoary/+source/'
75 ... 'evolution/+pots/man/es/1/+translate')
76 >>> inputradio = admin_browser.getControl(
77 ... name='msgset_166_es_translation_0_radiobutton')
78 >>> inputradio.value = ['msgset_166_es_translation_0_new']
79 >>> inputfield = admin_browser.getControl(
80 ... name='msgset_166_es_translation_0_new')
81 >>> inputfield.value = 'New test translation'
82 >>> admin_browser.getControl('Save & Continue').click()
83 >>> print extract_text(find_tag_by_id(admin_browser.contents,
84 ... 'messages_to_translate'))
85 1. English: test man page
86 Current Spanish: New test translation Translated and reviewed ...
87 New translation:
88 Someone should review this translation
89 Located in ...
90
91'Someone should review this translation' can be used to reset the translation,
92when a new emtpy translation is added and marked as needing review.
93After clicking 'Save & Continue', all translations entered for this
94message will be listed as suggestions.
95
96 >>> admin_browser.getControl(
97 ... 'Someone should review this translation').selected = True
98 >>> inputradio = admin_browser.getControl(
99 ... name='msgset_166_es_translation_0_radiobutton')
100 >>> inputradio.value = ['msgset_166_es_translation_0_new']
101 >>> admin_browser.getControl('Save & Continue').click()
102 >>> print extract_text(find_tag_by_id(admin_browser.contents,
103 ... 'messages_to_translate'))
104 1. English: test man page
105 Current Spanish: (no translation yet)
106 Suggestions:
107 New test translation Suggested by Foo Bar ...
108 blah, blah, blah Suggested by Carlos ...
109 lalalala Suggested by Carlos ...
110 just a translation Suggested by Sample Person ...
111 Dismiss all suggestions above.
112 New translation:
113 Someone should review this translation
114 Located in ...
56115
=== modified file 'lib/lp/translations/templates/pofile-translate.pt'
--- lib/lp/translations/templates/pofile-translate.pt 2010-03-22 17:44:53 +0000
+++ lib/lp/translations/templates/pofile-translate.pt 2010-04-13 13:02:29 +0000
@@ -18,33 +18,7 @@
18 registerLaunchpadFunction(insertAllExpansionButtons);18 registerLaunchpadFunction(insertAllExpansionButtons);
1919
20 LPS.use('lp.pofile', function(Y) {20 LPS.use('lp.pofile', function(Y) {
2121 Y.on('domready', Y.lp.pofile.initializePOFile);
22 Y.on('domready', function(e) {
23 try {
24 Y.lp.pofile.updateNotificationBox();
25 } catch (e) {
26 Y.log(e, "error");
27 }
28
29 try {
30 Y.lp.pofile.setupSuggestionDismissal();
31 } catch (e) {
32 Y.log(e, "error");
33 }
34
35 try {
36 Y.lp.pofile.initializeKeyBindings();
37 } catch (e) {
38 Y.log(e, "error");
39 }
40
41 try {
42 Y.lp.pofile.setFocus(autofocus_field);
43 } catch (e) {
44 Y.log(e, "error");
45 }
46 });
47
48 });22 });
4923
50 </script>24 </script>
5125
=== modified file 'lib/lp/translations/templates/translationmessage-translate.pt'
--- lib/lp/translations/templates/translationmessage-translate.pt 2010-03-22 17:44:53 +0000
+++ lib/lp/translations/templates/translationmessage-translate.pt 2010-04-13 13:02:29 +0000
@@ -15,25 +15,7 @@
15 </style>15 </style>
16 <script type="text/javascript">16 <script type="text/javascript">
17 LPS.use('node', 'lp.pofile', function(Y) {17 LPS.use('node', 'lp.pofile', function(Y) {
18 Y.on('domready', function(e) {18 Y.on('domready', Y.lp.pofile.initializeTranslationMessage);
19 try {
20 Y.lp.pofile.setupSuggestionDismissal();
21 } catch (e) {
22 Y.log(e, "error");
23 }
24
25 try {
26 Y.lp.pofile.initializeKeyBindings();
27 } catch (e) {
28 Y.log(e, "error");
29 }
30
31 try {
32 Y.lp.pofile.setFocus(autofocus_field);
33 } catch (e) {
34 Y.log(e, "error");
35 }
36 });
37 });19 });
38 </script>20 </script>
39 </div>21 </div>
4022
=== modified file 'lib/lp/translations/tests/test_potmsgset.py'
--- lib/lp/translations/tests/test_potmsgset.py 2010-04-12 16:13:38 +0000
+++ lib/lp/translations/tests/test_potmsgset.py 2010-04-13 13:02:29 +0000
@@ -908,6 +908,89 @@
908 include_dismissed=False, include_unreviewed=False))908 include_dismissed=False, include_unreviewed=False))
909909
910910
911class TestPOTMsgSetResetTranslation(TestCaseWithFactory):
912 """Test resetting the current translation."""
913
914 layer = ZopelessDatabaseLayer
915
916 def gen_now(self):
917 now = datetime.now(pytz.UTC)
918 while True:
919 yield now
920 now += timedelta(milliseconds=1)
921
922 def setUp(self):
923 # Create a product with all the boilerplate objects to be able to
924 # create TranslationMessage objects.
925 super(TestPOTMsgSetResetTranslation, self).setUp()
926 self.now = self.gen_now().next
927 self.foo = self.factory.makeProduct()
928 self.foo_main = self.factory.makeProductSeries(
929 name='main', product=self.foo)
930 self.foo.official_rosetta = True
931
932 self.potemplate = self.factory.makePOTemplate(
933 productseries=self.foo_main, name="messages")
934 self.potmsgset = self.factory.makePOTMsgSet(self.potemplate,
935 sequence=1)
936 self.pofile = self.factory.makePOFile('eo', self.potemplate)
937
938 def test_resetCurrentTranslation_shared(self):
939 # Resetting a shared current translation will change iscurrent=False
940 # and there will be no other current translations for this POTMsgSet.
941
942 translation = self.factory.makeTranslationMessage(
943 self.pofile, self.potmsgset, translations=[u'Shared translation'],
944 reviewer=self.factory.makePerson(),
945 is_imported=False, force_diverged=False,
946 date_updated=self.now())
947
948 self.potmsgset.resetCurrentTranslation(self.pofile, self.now())
949 current = self.potmsgset.getCurrentTranslationMessage(
950 self.potemplate, self.pofile.language)
951 self.assertTrue(current is None)
952 self.assertFalse(translation.is_current)
953 self.assertFalse(translation.is_imported)
954 self.assertTrue(translation.potemplate is None)
955
956 def test_resetCurrentTranslation_diverged_not_imported(self):
957 # Resettting a diverged current translation that was not imported, will
958 # change is_current to False and will make it shared.
959
960 translation = self.factory.makeTranslationMessage(
961 self.pofile, self.potmsgset, translations=[u'Diverged text'],
962 reviewer=self.factory.makePerson(),
963 is_imported=False, force_diverged=True,
964 date_updated=self.now())
965
966 self.potmsgset.resetCurrentTranslation(self.pofile, self.now())
967 current = self.potmsgset.getCurrentTranslationMessage(
968 self.potemplate, self.pofile.language)
969 self.assertTrue(current is None)
970 self.assertFalse(translation.is_current)
971 self.assertFalse(translation.is_imported)
972 self.assertTrue(translation.potemplate is None)
973
974 def test_resetCurrentTranslation_diverged_imported(self):
975 # Resetting a diverged current translation that was imported in
976 # Launchpad will change iscurrent to False but the translation
977 # message will be still diverged.
978
979 translation = self.factory.makeTranslationMessage(
980 self.pofile, self.potmsgset, translations=[u'Imported diverged'],
981 reviewer=self.factory.makePerson(),
982 is_imported=True, force_diverged=True,
983 date_updated=self.now())
984
985 self.potmsgset.resetCurrentTranslation(self.pofile, self.now())
986 current = self.potmsgset.getCurrentTranslationMessage(
987 self.potemplate, self.pofile.language)
988 self.assertTrue(current is None)
989 self.assertFalse(translation.is_current)
990 self.assertTrue(translation.is_imported)
991 self.assertFalse(translation.potemplate is None)
992
993
911class TestPOTMsgSetCornerCases(TestCaseWithFactory):994class TestPOTMsgSetCornerCases(TestCaseWithFactory):
912 """Test corner cases and constraints."""995 """Test corner cases and constraints."""
913996
914997
=== modified file 'lib/lp/translations/windmill/tests/test_pofile_translate.py'
--- lib/lp/translations/windmill/tests/test_pofile_translate.py 2010-04-11 20:07:25 +0000
+++ lib/lp/translations/windmill/tests/test_pofile_translate.py 2010-04-13 13:02:29 +0000
@@ -21,7 +21,7 @@
21 layer = TranslationsWindmillLayer21 layer = TranslationsWindmillLayer
22 suite_name = 'POFile Translate'22 suite_name = 'POFile Translate'
2323
24 def _check_translation_autoselect(24 def _checkTranslationAutoselect(
25 self, url, new_translation_id, new_translation_select_id):25 self, url, new_translation_id, new_translation_select_id):
26 """Checks that the select radio button is checked when typing a new26 """Checks that the select radio button is checked when typing a new
27 translation.27 translation.
@@ -60,7 +60,7 @@
60 'evolution/trunk/+pots/evolution-2.2/es/+translate')60 'evolution/trunk/+pots/evolution-2.2/es/+translate')
61 new_translation_id = u'msgset_1_es_translation_0_new'61 new_translation_id = u'msgset_1_es_translation_0_new'
62 new_translation_select_id = u'msgset_1_es_translation_0_new_select'62 new_translation_select_id = u'msgset_1_es_translation_0_new_select'
63 self._check_translation_autoselect(63 self._checkTranslationAutoselect(
64 start_url, new_translation_id, new_translation_select_id)64 start_url, new_translation_id, new_translation_select_id)
6565
66 # Test the zoom in view for Evolution trunk Brazilian (pt_BR).66 # Test the zoom in view for Evolution trunk Brazilian (pt_BR).
@@ -69,7 +69,7 @@
69 'pt_BR/1/+translate')69 'pt_BR/1/+translate')
70 new_translation_id = u'msgset_1_pt_BR_translation_0_new'70 new_translation_id = u'msgset_1_pt_BR_translation_0_new'
71 new_translation_select_id = u'msgset_1_pt_BR_translation_0_new_select'71 new_translation_select_id = u'msgset_1_pt_BR_translation_0_new_select'
72 self._check_translation_autoselect(72 self._checkTranslationAutoselect(
73 start_url, new_translation_id, new_translation_select_id)73 start_url, new_translation_id, new_translation_select_id)
7474
75 # Test the zoom out view for Ubuntu Hoary Brazilian (pt_BR).75 # Test the zoom out view for Ubuntu Hoary Brazilian (pt_BR).
@@ -79,5 +79,173 @@
79 new_translation_id = u'msgset_152_pt_BR_translation_0_new'79 new_translation_id = u'msgset_152_pt_BR_translation_0_new'
80 new_translation_select_id = (u'msgset_152_pt_BR'80 new_translation_select_id = (u'msgset_152_pt_BR'
81 '_translation_0_new_select')81 '_translation_0_new_select')
82 self._check_translation_autoselect(82 self._checkTranslationAutoselect(
83 start_url, new_translation_id, new_translation_select_id)83 start_url, new_translation_id, new_translation_select_id)
84
85 def _checkResetTranslationSelect(
86 self, client, checkbox, singular_new_select, singular_current_select,
87 singular_new_field=None, plural_new_select=None):
88 """Checks that the new translation select radio buttons are checked
89 when ticking 'Someone should review this translation' checkbox.
90 """
91
92 client.waits.forElement(
93 id=checkbox, timeout=constants.FOR_ELEMENT)
94 client.waits.forElement(
95 id=singular_new_select, timeout=constants.FOR_ELEMENT)
96 client.waits.forElement(
97 id=singular_current_select, timeout=constants.FOR_ELEMENT)
98 if plural_new_select is not None:
99 client.waits.forElement(
100 id=plural_new_select, timeout=constants.FOR_ELEMENT)
101 if singular_new_field is not None:
102 client.waits.forElement(
103 id=singular_new_field, timeout=constants.FOR_ELEMENT)
104
105 # Check that initialy the checkbox is not checked and
106 # that the radio buttons are not selected.
107 client.asserts.assertNotChecked(id=checkbox)
108 client.asserts.assertNotChecked(id=singular_new_select)
109 client.asserts.assertChecked(id=singular_current_select)
110 if plural_new_select is not None:
111 client.asserts.assertNotChecked(id=plural_new_select)
112
113 # Check the checkbox
114 client.click(id=checkbox)
115
116 # Check that the checkbox and the new translation radio buttons are
117 # selected.
118 client.asserts.assertChecked(id=checkbox)
119 client.asserts.assertChecked(id=singular_new_select)
120 client.asserts.assertNotChecked(id=singular_current_select)
121 if plural_new_select is not None:
122 client.asserts.assertChecked(id=plural_new_select)
123
124 # Then then we uncheck the 'Someone needs to review this translation'
125 # checkbox.
126 client.click(id=checkbox)
127
128 # Unchecking the 'Someone needs to review this translation' checkbox
129 # when the 'New translation' field is empty, will select the current
130 # translation.
131 client.asserts.assertNotChecked(id=checkbox)
132 client.asserts.assertNotChecked(id=singular_new_select)
133 client.asserts.assertChecked(id=singular_current_select)
134 if plural_new_select is not None:
135 client.asserts.assertNotChecked(id=plural_new_select)
136
137 if singular_new_field is not None:
138 # Checking again the 'Someone need to review this translation'
139 # checkbox, type some text and unchecking it should keep the new
140 # translation fields selected
141 client.click(id=checkbox)
142 client.type(text=u'some test', id=singular_new_field)
143 client.click(id=checkbox)
144
145 client.asserts.assertNotChecked(id=checkbox)
146 client.asserts.assertChecked(id=singular_new_select)
147 client.asserts.assertNotChecked(id=singular_current_select)
148 if plural_new_select is not None:
149 client.asserts.assertNotChecked(id=plural_new_select)
150
151 def test_pofile_reset_translation_select(self):
152 """Test for automatically selecting new translation when
153 'Someone needs to review this translations' is checked.
154 """
155 client = self.client
156 user = lpuser.TRANSLATIONS_ADMIN
157
158 # Go to the zoom in page for a translation with plural forms.
159 self.client.open(
160 url='http://translations.launchpad.dev:8085/'
161 'ubuntu/hoary/+source/evolution/+pots/'
162 'evolution-2.2/es/15/+translate')
163 self.client.waits.forPageLoad(timeout=constants.PAGE_LOAD)
164 user.ensure_login(self.client)
165
166 checkbox = u'msgset_144_force_suggestion'
167 singular_new_select = u'msgset_144_es_translation_0_new_select'
168 singular_new_field = u'msgset_144_es_translation_0_new'
169 singular_current_select = u'msgset_144_es_translation_0_radiobutton'
170 plural_new_select = u'msgset_144_es_translation_1_new_select'
171 self._checkResetTranslationSelect(
172 client,
173 checkbox=checkbox,
174 singular_new_select=singular_new_select,
175 singular_new_field=singular_new_field,
176 singular_current_select=singular_current_select,
177 plural_new_select=plural_new_select)
178
179 # Go to the zoom in page for a pt_BR translation with plural forms.
180 # pt_BR is a language code using the same delimiter as HTTP form
181 # fields and are prone to errors.
182 self.client.open(
183 url='http://translations.launchpad.dev:8085/'
184 'ubuntu/hoary/+source/evolution/+pots/'
185 'evolution-2.2/pt_BR/15/+translate')
186 self.client.waits.forPageLoad(timeout=constants.PAGE_LOAD)
187
188 checkbox = u'msgset_144_force_suggestion'
189 singular_new_select = u'msgset_144_pt_BR_translation_0_new_select'
190 singular_new_field = u'msgset_144_pt_BR_translation_0_new'
191 singular_current_select = u'msgset_144_pt_BR_translation_0_radiobutton'
192 plural_new_select = u'msgset_144_pt_BR_translation_1_new_select'
193 self._checkResetTranslationSelect(
194 client,
195 checkbox=checkbox,
196 singular_new_select=singular_new_select,
197 singular_new_field=singular_new_field,
198 singular_current_select=singular_current_select,
199 plural_new_select=plural_new_select)
200
201 # Go to the zoom in page for a translation without plural forms.
202 self.client.open(
203 url='http://translations.launchpad.dev:8085/'
204 'ubuntu/hoary/+source/evolution/+pots/'
205 'evolution-2.2/es/19/+translate')
206 self.client.waits.forPageLoad(timeout=constants.PAGE_LOAD)
207
208 checkbox = u'msgset_148_force_suggestion'
209 singular_new_select = u'msgset_148_es_translation_0_new_select'
210 singular_current_select = u'msgset_148_es_translation_0_radiobutton'
211 self._checkResetTranslationSelect(
212 client,
213 checkbox=checkbox,
214 singular_new_select=singular_new_select,
215 singular_current_select=singular_current_select)
216
217 # Go to the zoom out page for some translations.
218 self.client.open(
219 url='http://translations.launchpad.dev:8085/'
220 'ubuntu/hoary/+source/evolution/+pots/'
221 'evolution-2.2/es/+translate')
222 self.client.waits.forPageLoad(timeout=constants.PAGE_LOAD)
223
224 checkbox = u'msgset_130_force_suggestion'
225 singular_new_select = u'msgset_130_es_translation_0_new_select'
226 singular_current_select = u'msgset_130_es_translation_0_radiobutton'
227 self._checkResetTranslationSelect(
228 client,
229 checkbox=checkbox,
230 singular_new_select=singular_new_select,
231 singular_current_select=singular_current_select)
232
233 # Ensure that the other radio buttons are not changed
234 client.asserts.assertNotChecked(
235 id=u'msgset_131_es_translation_0_new_select')
236 client.asserts.assertNotChecked(
237 id=u'msgset_132_es_translation_0_new_select')
238 client.asserts.assertNotChecked(
239 id=u'msgset_133_es_translation_0_new_select')
240 client.asserts.assertNotChecked(
241 id=u'msgset_134_es_translation_0_new_select')
242 client.asserts.assertNotChecked(
243 id=u'msgset_135_es_translation_0_new_select')
244 client.asserts.assertNotChecked(
245 id=u'msgset_136_es_translation_0_new_select')
246 client.asserts.assertNotChecked(
247 id=u'msgset_137_es_translation_0_new_select')
248 client.asserts.assertNotChecked(
249 id=u'msgset_138_es_translation_0_new_select')
250 client.asserts.assertNotChecked(
251 id=u'msgset_139_es_translation_0_new_select')