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
1=== modified file 'lib/canonical/launchpad/javascript/translations/pofile.js'
2--- lib/canonical/launchpad/javascript/translations/pofile.js 2010-03-26 23:26:18 +0000
3+++ lib/canonical/launchpad/javascript/translations/pofile.js 2010-04-13 13:02:29 +0000
4@@ -13,7 +13,7 @@
5 * Function to disable/enable all suggestions as they are marked/unmarked
6 * for dismission.
7 */
8-self.setupSuggestionDismissal = function(e) {
9+var setupSuggestionDismissal = function(e) {
10 all_dismiss_boxes = Y.all('.dismiss_action');
11 if (all_dismiss_boxes !== null) {
12 all_dismiss_boxes.each(function(checkbox) {
13@@ -57,7 +57,8 @@
14 hide_anim.run();
15 };
16
17-self.updateNotificationBox = function(e) {
18+
19+var updateNotificationBox = function(e) {
20 var notice = Y.one('.important-notice-container');
21 if (notice === null) {
22 // We have no notice container on this page, this is why there is
23@@ -92,7 +93,7 @@
24 };
25
26
27-self.setFocus = function(field) {
28+var setFocus = function(field) {
29 // if there is nofield, do nothing
30 if (Y.one('#' + field) !== null) {
31 Y.one('#' + field).focus();
32@@ -101,7 +102,7 @@
33
34
35 var setNextFocus = function(e, field) {
36- self.setFocus(field);
37+ setFocus(field);
38 // stopPropagation() and preventDefault()
39 e.halt();
40 };
41@@ -111,8 +112,8 @@
42
43 // Original singular test is focused first to make sure
44 // it is visible when scrolling up
45- self.setFocus(original);
46- self.setFocus(field);
47+ setFocus(original);
48+ setFocus(field);
49 // stopPropagation() and preventDefault()
50 e.halt();
51 };
52@@ -180,7 +181,7 @@
53 };
54
55
56-var selectTranslation = function(e, widget_id) {
57+var selectTranslation = function(e, field) {
58 // Don't select when tabbing, navigating and simply pressing
59 // enter to submit the form.
60 // Looks like this is not needed for Epiphany and Chromium
61@@ -190,7 +191,8 @@
62 (e.shiftKey && e.altKey && e.keyCode == 75)) {
63 return;
64 }
65- selectWidgetByID(widget_id);
66+ var translation_select_id = field + '_select';
67+ selectWidgetByID(translation_select_id);
68 };
69
70
71@@ -204,11 +206,11 @@
72 }
73 // Shift+Alt+f - Go to search field
74 if (e.shiftKey && e.altKey && e.keyCode == 70) {
75- self.setFocus('search_box');
76+ setFocus('search_box');
77 }
78 // Shift+Alt+b - Go to first translation field
79 if (e.shiftKey && e.altKey && e.keyCode == 66) {
80- self.setFocus(fields[0]);
81+ setFocus(fields[0]);
82 }
83 // Shift+Alt+n - Go to next page in batch
84 if (e.shiftKey && e.altKey && e.keyCode == 78) {
85@@ -278,14 +280,13 @@
86 var html_parts = fields[key].split('_');
87 var msgset_id = html_parts[0] + '_' + html_parts[1];
88 var translation_stem = fields[key].replace(/_translation_(\d)+_new/,"");
89- var translation_select_id = fields[key] + '_select';
90
91 Y.on(
92 'change', selectTranslation,
93- '#' + fields[key], Y, translation_select_id);
94+ '#' + fields[key], Y, fields[key]);
95 Y.on(
96 'keypress', selectTranslation,
97- '#' + fields[key], Y, translation_select_id);
98+ '#' + fields[key], Y, fields[key]);
99
100 // Set next field and copy text for all but last field
101 // (last is Save & Continue button)
102@@ -346,11 +347,7 @@
103 };
104
105
106-/**
107- * Initialize event-key bindings such as moving to the next or previous
108- * field, or copying original text
109- */
110-self.initializeKeyBindings = function(e) {
111+var initializeKeyBindings = function(e) {
112
113 if (translations_order.length < 1) {
114 // If no translations fiels are displayed on the page
115@@ -366,5 +363,109 @@
116 initializeFieldsKeyBindings(fields);
117 };
118
119+
120+var initializeResetBehavior = function (fields) {
121+ for (var key = 0; key < fields.length; key++) {
122+ var html_parts = fields[key].split('_');
123+ var msgset_id = html_parts[0] + '_' + html_parts[1];
124+ var node = Y.one('#' + msgset_id + '_force_suggestion');
125+ if (node === null) {
126+ // If we don't have a force_suggestion checkbox associated with
127+ // this field, just continue to the next field.
128+ break;
129+ }
130+ Y.on(
131+ 'click',
132+ function (e, translation_id) {
133+ if (this === null) {
134+ // Don't do nothing if we don't have a context object.
135+ return;
136+ }
137+ if (this.get('checked') == true) {
138+ var new_translation_select = Y.one(
139+ '#' + translation_id + '_select');
140+ if (new_translation_select !== null) {
141+ new_translation_select.set('checked', true);
142+ }
143+ } else {
144+ var new_translation_field = Y.one('#' + translation_id);
145+ if (new_translation_field !== null &&
146+ new_translation_field.get('value') == '') {
147+ var current_select_id = translation_id.replace(
148+ /_new$/, '_radiobutton');
149+ var current_select = Y.one('#' + current_select_id);
150+ if (current_select !== null) {
151+ current_select.set('checked', true);
152+ }
153+ }
154+ }
155+ },
156+ node , node, fields[key]
157+ );
158+ }
159+};
160+
161+/**
162+ * Initialize common Javascript code for POFile and TranslationMessage
163+ * +translate pages.
164+ *
165+ * This will add event-key bindings such as moving to the next or previous
166+ * field, or copying original text.
167+ * It will also initializing the reset checkbox behavior and will show the
168+ * error notifications.
169+ */
170+
171+var initializeBaseTranslate = function () {
172+ try {
173+ setupSuggestionDismissal();
174+ } catch (e) {
175+ Y.log(e, "error");
176+ }
177+
178+ try {
179+ initializeKeyBindings();
180+ } catch (e) {
181+ Y.log(e, "error");
182+ }
183+
184+ try {
185+ var fields = translations_order.split(' ');
186+ initializeResetBehavior(fields);
187+ } catch (e) {
188+ Y.log(e, "error");
189+ }
190+
191+ try {
192+ setFocus(autofocus_field);
193+ } catch (e) {
194+ Y.log(e, "error");
195+ }
196+}
197+
198+/**
199+ * Initialize Javascript code for a POFile +translate page.
200+ *
201+ * This will initialize the base code and will also show the guidelines
202+ * if needeed.
203+ */
204+self.initializePOFile = function(e) {
205+ try {
206+ updateNotificationBox();
207+ } catch (e) {
208+ Y.log(e, "error");
209+ }
210+ initializeBaseTranslate();
211+};
212+
213+/**
214+ * Initialize Javascript code for a TranslationMessage +translate page.
215+ *
216+ * This will initialize the base code.
217+ */
218+self.initializeTranslationMessage = function(e) {
219+ initializeBaseTranslate();
220+};
221+
222+
223 }, "0.1", {"requires": ["event", "event-key", "node", "cookie", "anim"]});
224
225
226=== modified file 'lib/lp/translations/browser/tests/translationmessage-views.txt'
227--- lib/lp/translations/browser/tests/translationmessage-views.txt 2009-09-11 22:24:51 +0000
228+++ lib/lp/translations/browser/tests/translationmessage-views.txt 2010-04-13 13:02:29 +0000
229@@ -689,3 +689,101 @@
230 False
231 >>> subview.shared_translationmessage == translationmessage
232 True
233+
234+
235+Resetting current translation
236+-----------------------------
237+
238+The current translation can be reset by submitting an empty translation
239+or a translation similar to the current one while forcing suggestions.
240+
241+We need to force timestamp update, since suggestions are submitted too fast.
242+
243+ >>> from lp.translations.interfaces.translationgroup import (
244+ ... TranslationPermission)
245+ >>> pofile = factory.makePOFile('es')
246+ >>> potemplate = pofile.potemplate
247+ >>> potmsgset = factory.makePOTMsgSet(potemplate, sequence=1)
248+ >>> translationmessage = factory.makeTranslationMessage(
249+ ... potmsgset=potmsgset, pofile=pofile,
250+ ... translations = [u"A shared translation"])
251+ >>> translationmessage.setPOFile(pofile)
252+ >>> product = potemplate.productseries.product
253+ >>> product.translationpermission = TranslationPermission.CLOSED
254+ >>> year_tick = 2100
255+ >>> def submit_translation(
256+ ... translationmessage, translation, force_suggestion=False):
257+ ... global year_tick
258+ ... pofile = translationmessage.pofile
259+ ... potmsgset = translationmessage.potmsgset
260+ ... server_url = '/'.join(
261+ ... [canonical_url(translationmessage), '+translate'])
262+ ... now = (str(year_tick) +
263+ ... datetime.now(UTC).strftime('-%m-%dT%H:%M:%S+00:00'))
264+ ... year_tick += 1
265+ ... msgset_id = 'msgset_' + str(potmsgset.id)
266+ ... msgset_id_lang = msgset_id + '_' + pofile.language.code
267+ ... form = {
268+ ... 'lock_timestamp': now,
269+ ... 'alt': None,
270+ ... msgset_id : None,
271+ ... msgset_id_lang + '_translation_0_radiobutton':
272+ ... msgset_id_lang + '_translation_0_new',
273+ ... msgset_id_lang + '_translation_0_new': translation,
274+ ... 'submit_translations': 'Save &amp; Continue'}
275+ ... if force_suggestion:
276+ ... form[msgset_id_lang + '_needsreview'] = 'force_suggestion'
277+ ... tm_view = create_view(
278+ ... translationmessage, "+translate", form=form,
279+ ... layer=TranslationsLayer, server_url=server_url)
280+ ... tm_view.request.method = 'POST'
281+ ... tm_view.initialize()
282+
283+ >>> def print_current_translation(potmsgset, pofile):
284+ ... current_translation = potmsgset.getCurrentTranslationMessage(
285+ ... pofile.potemplate, pofile.language)
286+ ... if current_translation is not None:
287+ ... print current_translation.translations
288+
289+ >>> def print_local_suggestions(potmsgset, pofile):
290+ ... import operator
291+ ... local = sorted(
292+ ... potmsgset.getLocalTranslationMessages(
293+ ... pofile.potemplate, pofile.language),
294+ ... key=operator.attrgetter("date_created"),
295+ ... reverse=True)
296+ ... for suggestion in local:
297+ ... print suggestion.translations
298+
299+We will make an initial translation.
300+
301+ >>> submit_translation(
302+ ... translationmessage, u'Foo')
303+ >>> print_local_suggestions(potmsgset, pofile)
304+ >>> print_current_translation(potmsgset, pofile)
305+ [u'Foo']
306+
307+Forcing suggestions while submitting an empty translation will reset the
308+translation for this message and all previously entered translations will be
309+listed as suggestions.
310+
311+ >>> submit_translation(translationmessage, u'', force_suggestion=True)
312+ >>> print_local_suggestions(potmsgset, pofile)
313+ [u'Foo']
314+ [u'A shared translation']
315+ >>> print_current_translation(potmsgset, pofile)
316+
317+Only users with edit rights are allowed to reset a translation.
318+
319+ >>> submit_translation(translationmessage, u'New current translation')
320+ >>> print_current_translation(potmsgset, pofile)
321+ [u'New current translation']
322+ >>> print_local_suggestions(potmsgset, pofile)
323+ >>> login('no-priv@canonical.com')
324+ >>> submit_translation(
325+ ... translationmessage, u'New current translation',
326+ ... force_suggestion=True)
327+ >>> login('carlos@canonical.com')
328+ >>> print_local_suggestions(potmsgset, pofile)
329+ >>> print_current_translation(potmsgset, pofile)
330+ [u'New current translation']
331
332=== modified file 'lib/lp/translations/browser/translationmessage.py'
333--- lib/lp/translations/browser/translationmessage.py 2010-04-07 06:23:30 +0000
334+++ lib/lp/translations/browser/translationmessage.py 2010-04-13 13:02:29 +0000
335@@ -425,6 +425,16 @@
336 is_imported=False, lock_timestamp=self.lock_timestamp,
337 force_suggestion=force_suggestion,
338 force_diverged=force_diverge)
339+
340+ # If suggestions were forced and user has the rights to do it,
341+ # reset the current translation.
342+ empty_suggestions = self._areSuggestionsEmpty(translations)
343+ if (force_suggestion and
344+ self.user_is_official_translator and
345+ empty_suggestions):
346+ potmsgset.resetCurrentTranslation(
347+ self.pofile, self.lock_timestamp)
348+
349 except TranslationConflict:
350 return (
351 u'Somebody else changed this translation since you started.'
352@@ -439,6 +449,13 @@
353 self._observeTranslationUpdate(potmsgset)
354 return None
355
356+ def _areSuggestionsEmpty(self, suggestions):
357+ """Return true if all suggestions are empty strings or None."""
358+ for index in suggestions:
359+ if (suggestions[index] is not None and suggestions[index] != ""):
360+ return False
361+ return True
362+
363 def _prepareView(self, view_class, current_translation_message, error):
364 """Collect data and build a TranslationMessageView for display."""
365 # XXX: kiko 2006-09-27:
366
367=== modified file 'lib/lp/translations/interfaces/potmsgset.py'
368--- lib/lp/translations/interfaces/potmsgset.py 2010-03-05 15:31:22 +0000
369+++ lib/lp/translations/interfaces/potmsgset.py 2010-04-13 13:02:29 +0000
370@@ -250,6 +250,18 @@
371 If a translation conflict is detected, TranslationConflict is raised.
372 """
373
374+ def resetCurrentTranslation(pofile, lock_timestamp):
375+ """Reset the currently used translation.
376+
377+ This will set the "is_current" attribute to False and if the message
378+ is diverge, will try to converge it.
379+ :param pofile: a `POFile` to dismiss suggestions from.
380+ :param lock_timestamp: the timestamp when we checked the values we
381+ want to update.
382+
383+ If a translation conflict is detected, TranslationConflict is raised.
384+ """
385+
386 def applySanityFixes(unicode_text):
387 """Return 'unicode_text' or None after doing some sanitization.
388
389
390=== modified file 'lib/lp/translations/model/potmsgset.py'
391--- lib/lp/translations/model/potmsgset.py 2010-03-05 15:31:22 +0000
392+++ lib/lp/translations/model/potmsgset.py 2010-04-13 13:02:29 +0000
393@@ -883,6 +883,24 @@
394 matching_message.sync()
395 return matching_message
396
397+ def _maybeRaiseTranslationConflict(self, message, lock_timestamp):
398+ """Checks if there is a translation conflict for the message.
399+
400+ If a translation conflict is detected, TranslationConflict is raised.
401+ """
402+ if message.date_reviewed is not None:
403+ use_date = message.date_reviewed
404+ else:
405+ use_date = message.date_created
406+ if use_date >= lock_timestamp:
407+ raise TranslationConflict(
408+ 'While you were reviewing these suggestions, somebody '
409+ 'else changed the actual translation. This is not an '
410+ 'error but you might want to re-review the strings '
411+ 'concerned.')
412+ else:
413+ return
414+
415 def dismissAllSuggestions(self, pofile, reviewer, lock_timestamp):
416 """See `IPOTMsgSet`."""
417 assert(lock_timestamp is not None)
418@@ -893,19 +911,29 @@
419 current = self.updateTranslation(
420 pofile, reviewer, [], False, lock_timestamp)
421 else:
422- if current.date_reviewed is not None:
423- use_date = current.date_reviewed
424- else:
425- use_date = current.date_created
426- if use_date >= lock_timestamp:
427- raise TranslationConflict(
428- 'While you were reviewing these suggestions, somebody '
429- 'else changed the actual translation. This is not an '
430- 'error but you might want to re-review the strings '
431- 'concerned.')
432- else:
433- current.reviewer = reviewer
434- current.date_reviewed = lock_timestamp
435+ # Check for translation conflicts and update review fields.
436+ self._maybeRaiseTranslationConflict(current, lock_timestamp)
437+ current.reviewer = reviewer
438+ current.date_reviewed = lock_timestamp
439+
440+ def resetCurrentTranslation(self, pofile, lock_timestamp):
441+ """See `IPOTMsgSet`."""
442+
443+ assert(lock_timestamp is not None)
444+
445+ current = self.getCurrentTranslationMessage(
446+ pofile.potemplate, pofile.language)
447+
448+ if (current is not None):
449+ # Check for transltion conflicts and update the required
450+ # attributes.
451+ self._maybeRaiseTranslationConflict(current, lock_timestamp)
452+ current.is_current = False
453+ # Converge the current translation only if it is diverged and not
454+ # imported.
455+ if current.potemplate is not None and not current.is_imported:
456+ current.potemplate = None
457+ pofile.date_changed = UTC_NOW
458
459 def applySanityFixes(self, text):
460 """See `IPOTMsgSet`."""
461
462=== modified file 'lib/lp/translations/model/translationmessage.py'
463--- lib/lp/translations/model/translationmessage.py 2009-10-28 13:08:05 +0000
464+++ lib/lp/translations/model/translationmessage.py 2010-04-13 13:02:29 +0000
465@@ -478,12 +478,12 @@
466 implements(ITranslationMessageSet)
467
468 def getByID(self, ID):
469- """See `ILanguageSet`."""
470+ """See `ITranslationMessageSet`."""
471 try:
472 return TranslationMessage.get(ID)
473 except SQLObjectNotFound:
474 return None
475
476 def selectDirect(self, where=None, order_by=None):
477- """See `ILanguageSet`."""
478+ """See `ITranslationMessageSet`."""
479 return TranslationMessage.select(where, orderBy=order_by)
480
481=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-needs-review-flags-preserved.txt'
482--- lib/lp/translations/stories/standalone/xx-pofile-translate-needs-review-flags-preserved.txt 2009-07-01 20:45:39 +0000
483+++ lib/lp/translations/stories/standalone/xx-pofile-translate-needs-review-flags-preserved.txt 2010-04-13 13:02:29 +0000
484@@ -1,6 +1,10 @@
485-= Forcing suggestions =
486-
487-When translation is restricted to a particular set of users
488+Someone should review this translation usage
489+============================================
490+
491+Forcing suggestions
492+-------------------
493+
494+When translating is restricted to a particular set of users
495 (like Evolution product is restricted for Spanish), other users
496 don't see a checkbox named 'Someone should review this translation'.
497
498@@ -53,3 +57,58 @@
499 >>> print extract_text(find_tag_by_id(user_browser.contents,
500 ... 'msgset_130_es_suggestion_701_0'))
501 New suggestion
502+
503+
504+Reseting translations
505+---------------------
506+
507+If the 'Someone should review this translation' checkbox is used without
508+adding any new suggestions or adding an empty string, the current translation
509+will be reset causing all suggestions entered for this message to be listed
510+again.
511+
512+A new translation is entered and checked that it was saved as the current
513+translation, while no suggestions are displayed.
514+
515+ >>> admin_browser.open(
516+ ... 'http://translations.launchpad.dev/ubuntu/hoary/+source/'
517+ ... 'evolution/+pots/man/es/1/+translate')
518+ >>> inputradio = admin_browser.getControl(
519+ ... name='msgset_166_es_translation_0_radiobutton')
520+ >>> inputradio.value = ['msgset_166_es_translation_0_new']
521+ >>> inputfield = admin_browser.getControl(
522+ ... name='msgset_166_es_translation_0_new')
523+ >>> inputfield.value = 'New test translation'
524+ >>> admin_browser.getControl('Save & Continue').click()
525+ >>> print extract_text(find_tag_by_id(admin_browser.contents,
526+ ... 'messages_to_translate'))
527+ 1. English: test man page
528+ Current Spanish: New test translation Translated and reviewed ...
529+ New translation:
530+ Someone should review this translation
531+ Located in ...
532+
533+'Someone should review this translation' can be used to reset the translation,
534+when a new emtpy translation is added and marked as needing review.
535+After clicking 'Save & Continue', all translations entered for this
536+message will be listed as suggestions.
537+
538+ >>> admin_browser.getControl(
539+ ... 'Someone should review this translation').selected = True
540+ >>> inputradio = admin_browser.getControl(
541+ ... name='msgset_166_es_translation_0_radiobutton')
542+ >>> inputradio.value = ['msgset_166_es_translation_0_new']
543+ >>> admin_browser.getControl('Save & Continue').click()
544+ >>> print extract_text(find_tag_by_id(admin_browser.contents,
545+ ... 'messages_to_translate'))
546+ 1. English: test man page
547+ Current Spanish: (no translation yet)
548+ Suggestions:
549+ New test translation Suggested by Foo Bar ...
550+ blah, blah, blah Suggested by Carlos ...
551+ lalalala Suggested by Carlos ...
552+ just a translation Suggested by Sample Person ...
553+ Dismiss all suggestions above.
554+ New translation:
555+ Someone should review this translation
556+ Located in ...
557
558=== modified file 'lib/lp/translations/templates/pofile-translate.pt'
559--- lib/lp/translations/templates/pofile-translate.pt 2010-03-22 17:44:53 +0000
560+++ lib/lp/translations/templates/pofile-translate.pt 2010-04-13 13:02:29 +0000
561@@ -18,33 +18,7 @@
562 registerLaunchpadFunction(insertAllExpansionButtons);
563
564 LPS.use('lp.pofile', function(Y) {
565-
566- Y.on('domready', function(e) {
567- try {
568- Y.lp.pofile.updateNotificationBox();
569- } catch (e) {
570- Y.log(e, "error");
571- }
572-
573- try {
574- Y.lp.pofile.setupSuggestionDismissal();
575- } catch (e) {
576- Y.log(e, "error");
577- }
578-
579- try {
580- Y.lp.pofile.initializeKeyBindings();
581- } catch (e) {
582- Y.log(e, "error");
583- }
584-
585- try {
586- Y.lp.pofile.setFocus(autofocus_field);
587- } catch (e) {
588- Y.log(e, "error");
589- }
590- });
591-
592+ Y.on('domready', Y.lp.pofile.initializePOFile);
593 });
594
595 </script>
596
597=== modified file 'lib/lp/translations/templates/translationmessage-translate.pt'
598--- lib/lp/translations/templates/translationmessage-translate.pt 2010-03-22 17:44:53 +0000
599+++ lib/lp/translations/templates/translationmessage-translate.pt 2010-04-13 13:02:29 +0000
600@@ -15,25 +15,7 @@
601 </style>
602 <script type="text/javascript">
603 LPS.use('node', 'lp.pofile', function(Y) {
604- Y.on('domready', function(e) {
605- try {
606- Y.lp.pofile.setupSuggestionDismissal();
607- } catch (e) {
608- Y.log(e, "error");
609- }
610-
611- try {
612- Y.lp.pofile.initializeKeyBindings();
613- } catch (e) {
614- Y.log(e, "error");
615- }
616-
617- try {
618- Y.lp.pofile.setFocus(autofocus_field);
619- } catch (e) {
620- Y.log(e, "error");
621- }
622- });
623+ Y.on('domready', Y.lp.pofile.initializeTranslationMessage);
624 });
625 </script>
626 </div>
627
628=== modified file 'lib/lp/translations/tests/test_potmsgset.py'
629--- lib/lp/translations/tests/test_potmsgset.py 2010-04-12 16:13:38 +0000
630+++ lib/lp/translations/tests/test_potmsgset.py 2010-04-13 13:02:29 +0000
631@@ -908,6 +908,89 @@
632 include_dismissed=False, include_unreviewed=False))
633
634
635+class TestPOTMsgSetResetTranslation(TestCaseWithFactory):
636+ """Test resetting the current translation."""
637+
638+ layer = ZopelessDatabaseLayer
639+
640+ def gen_now(self):
641+ now = datetime.now(pytz.UTC)
642+ while True:
643+ yield now
644+ now += timedelta(milliseconds=1)
645+
646+ def setUp(self):
647+ # Create a product with all the boilerplate objects to be able to
648+ # create TranslationMessage objects.
649+ super(TestPOTMsgSetResetTranslation, self).setUp()
650+ self.now = self.gen_now().next
651+ self.foo = self.factory.makeProduct()
652+ self.foo_main = self.factory.makeProductSeries(
653+ name='main', product=self.foo)
654+ self.foo.official_rosetta = True
655+
656+ self.potemplate = self.factory.makePOTemplate(
657+ productseries=self.foo_main, name="messages")
658+ self.potmsgset = self.factory.makePOTMsgSet(self.potemplate,
659+ sequence=1)
660+ self.pofile = self.factory.makePOFile('eo', self.potemplate)
661+
662+ def test_resetCurrentTranslation_shared(self):
663+ # Resetting a shared current translation will change iscurrent=False
664+ # and there will be no other current translations for this POTMsgSet.
665+
666+ translation = self.factory.makeTranslationMessage(
667+ self.pofile, self.potmsgset, translations=[u'Shared translation'],
668+ reviewer=self.factory.makePerson(),
669+ is_imported=False, force_diverged=False,
670+ date_updated=self.now())
671+
672+ self.potmsgset.resetCurrentTranslation(self.pofile, self.now())
673+ current = self.potmsgset.getCurrentTranslationMessage(
674+ self.potemplate, self.pofile.language)
675+ self.assertTrue(current is None)
676+ self.assertFalse(translation.is_current)
677+ self.assertFalse(translation.is_imported)
678+ self.assertTrue(translation.potemplate is None)
679+
680+ def test_resetCurrentTranslation_diverged_not_imported(self):
681+ # Resettting a diverged current translation that was not imported, will
682+ # change is_current to False and will make it shared.
683+
684+ translation = self.factory.makeTranslationMessage(
685+ self.pofile, self.potmsgset, translations=[u'Diverged text'],
686+ reviewer=self.factory.makePerson(),
687+ is_imported=False, force_diverged=True,
688+ date_updated=self.now())
689+
690+ self.potmsgset.resetCurrentTranslation(self.pofile, self.now())
691+ current = self.potmsgset.getCurrentTranslationMessage(
692+ self.potemplate, self.pofile.language)
693+ self.assertTrue(current is None)
694+ self.assertFalse(translation.is_current)
695+ self.assertFalse(translation.is_imported)
696+ self.assertTrue(translation.potemplate is None)
697+
698+ def test_resetCurrentTranslation_diverged_imported(self):
699+ # Resetting a diverged current translation that was imported in
700+ # Launchpad will change iscurrent to False but the translation
701+ # message will be still diverged.
702+
703+ translation = self.factory.makeTranslationMessage(
704+ self.pofile, self.potmsgset, translations=[u'Imported diverged'],
705+ reviewer=self.factory.makePerson(),
706+ is_imported=True, force_diverged=True,
707+ date_updated=self.now())
708+
709+ self.potmsgset.resetCurrentTranslation(self.pofile, self.now())
710+ current = self.potmsgset.getCurrentTranslationMessage(
711+ self.potemplate, self.pofile.language)
712+ self.assertTrue(current is None)
713+ self.assertFalse(translation.is_current)
714+ self.assertTrue(translation.is_imported)
715+ self.assertFalse(translation.potemplate is None)
716+
717+
718 class TestPOTMsgSetCornerCases(TestCaseWithFactory):
719 """Test corner cases and constraints."""
720
721
722=== modified file 'lib/lp/translations/windmill/tests/test_pofile_translate.py'
723--- lib/lp/translations/windmill/tests/test_pofile_translate.py 2010-04-11 20:07:25 +0000
724+++ lib/lp/translations/windmill/tests/test_pofile_translate.py 2010-04-13 13:02:29 +0000
725@@ -21,7 +21,7 @@
726 layer = TranslationsWindmillLayer
727 suite_name = 'POFile Translate'
728
729- def _check_translation_autoselect(
730+ def _checkTranslationAutoselect(
731 self, url, new_translation_id, new_translation_select_id):
732 """Checks that the select radio button is checked when typing a new
733 translation.
734@@ -60,7 +60,7 @@
735 'evolution/trunk/+pots/evolution-2.2/es/+translate')
736 new_translation_id = u'msgset_1_es_translation_0_new'
737 new_translation_select_id = u'msgset_1_es_translation_0_new_select'
738- self._check_translation_autoselect(
739+ self._checkTranslationAutoselect(
740 start_url, new_translation_id, new_translation_select_id)
741
742 # Test the zoom in view for Evolution trunk Brazilian (pt_BR).
743@@ -69,7 +69,7 @@
744 'pt_BR/1/+translate')
745 new_translation_id = u'msgset_1_pt_BR_translation_0_new'
746 new_translation_select_id = u'msgset_1_pt_BR_translation_0_new_select'
747- self._check_translation_autoselect(
748+ self._checkTranslationAutoselect(
749 start_url, new_translation_id, new_translation_select_id)
750
751 # Test the zoom out view for Ubuntu Hoary Brazilian (pt_BR).
752@@ -79,5 +79,173 @@
753 new_translation_id = u'msgset_152_pt_BR_translation_0_new'
754 new_translation_select_id = (u'msgset_152_pt_BR'
755 '_translation_0_new_select')
756- self._check_translation_autoselect(
757+ self._checkTranslationAutoselect(
758 start_url, new_translation_id, new_translation_select_id)
759+
760+ def _checkResetTranslationSelect(
761+ self, client, checkbox, singular_new_select, singular_current_select,
762+ singular_new_field=None, plural_new_select=None):
763+ """Checks that the new translation select radio buttons are checked
764+ when ticking 'Someone should review this translation' checkbox.
765+ """
766+
767+ client.waits.forElement(
768+ id=checkbox, timeout=constants.FOR_ELEMENT)
769+ client.waits.forElement(
770+ id=singular_new_select, timeout=constants.FOR_ELEMENT)
771+ client.waits.forElement(
772+ id=singular_current_select, timeout=constants.FOR_ELEMENT)
773+ if plural_new_select is not None:
774+ client.waits.forElement(
775+ id=plural_new_select, timeout=constants.FOR_ELEMENT)
776+ if singular_new_field is not None:
777+ client.waits.forElement(
778+ id=singular_new_field, timeout=constants.FOR_ELEMENT)
779+
780+ # Check that initialy the checkbox is not checked and
781+ # that the radio buttons are not selected.
782+ client.asserts.assertNotChecked(id=checkbox)
783+ client.asserts.assertNotChecked(id=singular_new_select)
784+ client.asserts.assertChecked(id=singular_current_select)
785+ if plural_new_select is not None:
786+ client.asserts.assertNotChecked(id=plural_new_select)
787+
788+ # Check the checkbox
789+ client.click(id=checkbox)
790+
791+ # Check that the checkbox and the new translation radio buttons are
792+ # selected.
793+ client.asserts.assertChecked(id=checkbox)
794+ client.asserts.assertChecked(id=singular_new_select)
795+ client.asserts.assertNotChecked(id=singular_current_select)
796+ if plural_new_select is not None:
797+ client.asserts.assertChecked(id=plural_new_select)
798+
799+ # Then then we uncheck the 'Someone needs to review this translation'
800+ # checkbox.
801+ client.click(id=checkbox)
802+
803+ # Unchecking the 'Someone needs to review this translation' checkbox
804+ # when the 'New translation' field is empty, will select the current
805+ # translation.
806+ client.asserts.assertNotChecked(id=checkbox)
807+ client.asserts.assertNotChecked(id=singular_new_select)
808+ client.asserts.assertChecked(id=singular_current_select)
809+ if plural_new_select is not None:
810+ client.asserts.assertNotChecked(id=plural_new_select)
811+
812+ if singular_new_field is not None:
813+ # Checking again the 'Someone need to review this translation'
814+ # checkbox, type some text and unchecking it should keep the new
815+ # translation fields selected
816+ client.click(id=checkbox)
817+ client.type(text=u'some test', id=singular_new_field)
818+ client.click(id=checkbox)
819+
820+ client.asserts.assertNotChecked(id=checkbox)
821+ client.asserts.assertChecked(id=singular_new_select)
822+ client.asserts.assertNotChecked(id=singular_current_select)
823+ if plural_new_select is not None:
824+ client.asserts.assertNotChecked(id=plural_new_select)
825+
826+ def test_pofile_reset_translation_select(self):
827+ """Test for automatically selecting new translation when
828+ 'Someone needs to review this translations' is checked.
829+ """
830+ client = self.client
831+ user = lpuser.TRANSLATIONS_ADMIN
832+
833+ # Go to the zoom in page for a translation with plural forms.
834+ self.client.open(
835+ url='http://translations.launchpad.dev:8085/'
836+ 'ubuntu/hoary/+source/evolution/+pots/'
837+ 'evolution-2.2/es/15/+translate')
838+ self.client.waits.forPageLoad(timeout=constants.PAGE_LOAD)
839+ user.ensure_login(self.client)
840+
841+ checkbox = u'msgset_144_force_suggestion'
842+ singular_new_select = u'msgset_144_es_translation_0_new_select'
843+ singular_new_field = u'msgset_144_es_translation_0_new'
844+ singular_current_select = u'msgset_144_es_translation_0_radiobutton'
845+ plural_new_select = u'msgset_144_es_translation_1_new_select'
846+ self._checkResetTranslationSelect(
847+ client,
848+ checkbox=checkbox,
849+ singular_new_select=singular_new_select,
850+ singular_new_field=singular_new_field,
851+ singular_current_select=singular_current_select,
852+ plural_new_select=plural_new_select)
853+
854+ # Go to the zoom in page for a pt_BR translation with plural forms.
855+ # pt_BR is a language code using the same delimiter as HTTP form
856+ # fields and are prone to errors.
857+ self.client.open(
858+ url='http://translations.launchpad.dev:8085/'
859+ 'ubuntu/hoary/+source/evolution/+pots/'
860+ 'evolution-2.2/pt_BR/15/+translate')
861+ self.client.waits.forPageLoad(timeout=constants.PAGE_LOAD)
862+
863+ checkbox = u'msgset_144_force_suggestion'
864+ singular_new_select = u'msgset_144_pt_BR_translation_0_new_select'
865+ singular_new_field = u'msgset_144_pt_BR_translation_0_new'
866+ singular_current_select = u'msgset_144_pt_BR_translation_0_radiobutton'
867+ plural_new_select = u'msgset_144_pt_BR_translation_1_new_select'
868+ self._checkResetTranslationSelect(
869+ client,
870+ checkbox=checkbox,
871+ singular_new_select=singular_new_select,
872+ singular_new_field=singular_new_field,
873+ singular_current_select=singular_current_select,
874+ plural_new_select=plural_new_select)
875+
876+ # Go to the zoom in page for a translation without plural forms.
877+ self.client.open(
878+ url='http://translations.launchpad.dev:8085/'
879+ 'ubuntu/hoary/+source/evolution/+pots/'
880+ 'evolution-2.2/es/19/+translate')
881+ self.client.waits.forPageLoad(timeout=constants.PAGE_LOAD)
882+
883+ checkbox = u'msgset_148_force_suggestion'
884+ singular_new_select = u'msgset_148_es_translation_0_new_select'
885+ singular_current_select = u'msgset_148_es_translation_0_radiobutton'
886+ self._checkResetTranslationSelect(
887+ client,
888+ checkbox=checkbox,
889+ singular_new_select=singular_new_select,
890+ singular_current_select=singular_current_select)
891+
892+ # Go to the zoom out page for some translations.
893+ self.client.open(
894+ url='http://translations.launchpad.dev:8085/'
895+ 'ubuntu/hoary/+source/evolution/+pots/'
896+ 'evolution-2.2/es/+translate')
897+ self.client.waits.forPageLoad(timeout=constants.PAGE_LOAD)
898+
899+ checkbox = u'msgset_130_force_suggestion'
900+ singular_new_select = u'msgset_130_es_translation_0_new_select'
901+ singular_current_select = u'msgset_130_es_translation_0_radiobutton'
902+ self._checkResetTranslationSelect(
903+ client,
904+ checkbox=checkbox,
905+ singular_new_select=singular_new_select,
906+ singular_current_select=singular_current_select)
907+
908+ # Ensure that the other radio buttons are not changed
909+ client.asserts.assertNotChecked(
910+ id=u'msgset_131_es_translation_0_new_select')
911+ client.asserts.assertNotChecked(
912+ id=u'msgset_132_es_translation_0_new_select')
913+ client.asserts.assertNotChecked(
914+ id=u'msgset_133_es_translation_0_new_select')
915+ client.asserts.assertNotChecked(
916+ id=u'msgset_134_es_translation_0_new_select')
917+ client.asserts.assertNotChecked(
918+ id=u'msgset_135_es_translation_0_new_select')
919+ client.asserts.assertNotChecked(
920+ id=u'msgset_136_es_translation_0_new_select')
921+ client.asserts.assertNotChecked(
922+ id=u'msgset_137_es_translation_0_new_select')
923+ client.asserts.assertNotChecked(
924+ id=u'msgset_138_es_translation_0_new_select')
925+ client.asserts.assertNotChecked(
926+ id=u'msgset_139_es_translation_0_new_select')