Merge lp:~adiroiban/launchpad/bug-201749 into lp:launchpad
- bug-201749
- Merge into devel
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 |
Related bugs: |
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:
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:
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:/
== Implementation details ==
potmsgset.
translationmess
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:/
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.
Данило Шеган (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_
That's the other option, without making this too complex, but I leave that to the two of you to decide which is better.
Adi Roiban (adiroiban) wrote : | # |
> -----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:/
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_
> > + ... global year_tick
> > + ... translationmessage = TranslationMess
>
> 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_
> > + (self._
> > + (translationmessage is not None and
> > + self._areSugges
> > + translationmess
>
> 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...
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 _isNoTranslatio
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
Adi Roiban (adiroiban) 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.
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 _isNoTranslatio
> 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
> "_maybeRaiseTra
> first because it is clear that this may raise an exception.
Renamed to _maybeRaiseTran
> 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:/
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/
--- lib/canonical/
+++ lib/canonical/
@@ -321,12 +321,41 @@
}
}
-}
+};
+var initializeReset
+ for (var key = 0; key < fields.length; key++) {
+ var html_parts ...
Adi Roiban (adiroiban) wrote : | # |
Here are the Windmill tests and the changes in the pofile and translation +message javascript initialization.
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -50,3 +50,132 @@
# Check that the associated radio button is selected.
+
+ def _check_
+ self, client, checkbox, singular_select, singular_current,
+ plural_
+ """Checks that the new translation select radio buttons are checked
+ when ticking 'Someone should review this translation' checkbox.
+ """
+
+ client.
+ id=checkbox, timeout=
+ client.
+ id=singular_select, timeout=
+ if plural_select is not None:
+ client.
+ id=plural_select, timeout=
+
+ # Check that initialy the checkbox is not checked and
+ # that the radio buttons are not selected.
+ client.
+ client.
+ client.
+ if plural_select is not None:
+ client.
+
+ # Check the checkbox
+ client.
+
+ # Check that the checkbox and the new translation radio buttons are
+ # selected.
+ client.
+ client.
+ client.
+ if plural_select is not None:
+ client.
+
+ # We select the current translation for the singular form.
+ client.
+
+ # Then then we uncheck the 'Someone needs to review this translation'
+ # checkbox.
+ client.
+
+ # Unchecking the 'Someone needs to review this translation' checkbox
+ # will not change the state of the radio buttons.
+ client.
+ client.
+ client.
+ if plural_select is not None:
+ client.
+
+ def test_pofile_
+ """Test for automatically selecting new translation when
+ 'Someone needs to review this translations' is checked.
+ """
+ client = self.client
+ user = lpuser.
+
+ # Go to the zoom in page for a translation with plural forms.
+ self.client.open(
+ url='http://
+ 'ubuntu/
+ 'evolution-
Henning Eggers (henninge) wrote : | # |
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/
> --- lib/lp/
> +++ lib/lp/
> @@ -50,3 +50,132 @@
>
> # Check that the associated radio button is selected.
> client.
> +
> + def _check_
[...]
> + # Then then we uncheck the 'Someone needs to review this translation'
> + # checkbox.
> + client.
> +
> + # 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.
> + client.
> + client.
> + if plural_select is not None:
> + client.
> +
> + def test_pofile_
[...]
> === modified file 'lib/lp/
> === modified file 'lib/lp/
Thanks for moving the code out of the templates!
> === modified file 'lib/canonical/
[...]
> +
> +var initializeReset
> + for (var key = 0; key< fields.length; key++) {
> + var html_parts = fields[
> + var msgset_id = html_parts[0] + '_' + html_parts[1];
> + var node = Y.one('#' + msgset_id + '_force_
> + 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(
> + var select = Y.one('#' + translation_id + '_select');
> + if (select !== null) {
> + select.
> + }
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 {
Adi Roiban (adiroiban) wrote : | # |
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/
--- lib/canonical/
+++ lib/canonical/
@@ -374,10 +374,25 @@
}
if (this.get(
+ // When checking the "Need review" checkbox the new
+ // translation should be automatically checked.
+ } 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.
+ var current_radio_id = translation_
+ /_new$/, '_radiobutton');
+ var current_radio = Y.one('#' + current_radio_id);
+ if (current_radio !== null) {
+ current_
+ }
+ }
}
},
node , node, fields[key]
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -52,8 +52,8 @@
def _check_
- self, client, checkbox, singular_select, singular_current,
- plural_
+ self, client, checkbox, singular_
+ singular_
"""Checks that the new translation select radio buttons are checked
when ticking 'Someone should review this translation' checkbox.
"""
@@ -61,44 +61,61 @@
- id=singular_select, timeout=
- if plural_select is not None:
- client.
- id=plural_select, timeout=
+ id=singular_
+ client.
+ id=singular_
+ if plural_new_select is not None:
+ client.
+ id=plural_
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/
> --- lib/canonical/
> +++ lib/canonical/
> @@ -374,10 +374,25 @@
> return;
> }
> if (this.get(
> + // 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/
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.
> }
> + } 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.
> + var current_radio_id = translation_
> + /_new$/, '_radiobutton');
> + var current_radio = Y.one('#' + current_radio_id);
> + if (current_radio !== null) {
> + current_
> + }
> + }
> }
> },
> node , node, fields[key]
>
> === modified file 'lib/lp/
> --- lib/lp/
> +++ lib/lp/
> @@ -52,8 +52,8 @@
> client.
>
> def _check_
Sorry, I did not notice this before. Methods are always camelCase, functions
and variables use_underscores. It's in the style guide ... ;-)
def _checkResetTran
That's all.
Adi Roiban (adiroiban) wrote : | # |
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_
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/
--- lib/canonical/
+++ lib/canonical/
@@ -374,23 +374,20 @@
}
if (this.get(
- // When checking the "Need review" checkbox the new
- // translation should be automatically checked.
- var select = Y.one('#' + translation_id + '_select');
- if (select !== null) {
- select.
+ var new_translation
+ '#' + translation_id + '_select');
+ if (new_translatio
+ new_translation
} 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.
- var current_radio_id = translation_
+ var new_translation
+ if (new_translatio
+ new_translation
+ var current_select_id = translation_
- var current_radio = Y.one('#' + current_radio_id);
- if (current_radio !== null) {
- current_
+ var current_select = Y.one('#' + current_select_id);
+ if (current_select !== null) {
+ current_
}
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -51,7 +51,7 @@
# Check that the associated radio button is selected.
- def _check_
+ def _checkResetTran
self, client, checkbox, singular_
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_
> 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
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/
--- lib/lp/
+++ lib/lp/
@@ -75,8 +75,9 @@
... 'evolution/
>>> inputradio = admin_browser.
... name='msgset_
- >>> inputradio.value = [ 'msgset_
- >>> inputfield = admin_browser.
+ >>> inputradio.value = ['msgset_
+ >>> inputfield = admin_browser.
+ ... name='msgset_
>>> inputfield.value = 'New test translation'
>>> admin_browser.
>>> print extract_
@@ -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.
... 'Someone should review this translation'
+ >>> inputradio = admin_browser.
+ ... name='msgset_
+ >>> inputradio.value = ['msgset_
>>> admin_browser.
>>> print extract_
... 'messages_
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.
Adi Roiban (adiroiban) wrote : | # |
Hi,
I merged and solved the conflict and push the changes.
Cheers,
Adi
Preview Diff
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 & 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') |
-----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/translati onmessage- views.txt' translations/ browser/ tests/translati onmessage- views.txt 2009-09-11 22:24:51 +0000 translations/ browser/ tests/translati onmessage- views.txt 2010-03-15 13:43:34 +0000 shared_ translationmess age == translationmessage
> --- lib/lp/
> +++ lib/lp/
> @@ -689,3 +689,108 @@
> False
> >>> subview.
> 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 translation( translation, force_suggestio n=False) : age.get( 1)
> + >>> def submit_
> + ... global year_tick
> + ... translationmessage = TranslationMess
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 = translationmess age.pofile age.potmsgset url(translation message) , '+translate']) now(UTC) .strftime( '-%m-%dT% H:%M:%S+ 00:00') ) 1_es_translatio n_0_radiobutton ': 1_es_translatio n_0_new' , 1_es_translatio n_0_new' : translation, translations' : 'Save & Continue'} 1_es_needsrevie w'] = 'force_suggestion' onsLayer, server_ url=server_ url) request. method = 'POST' initialize( ) trans.. .
> + ... potmsgset = translationmess
> + ... server_url = '/'.join(
> + ... [canonical_
> + ... now = (str(year_tick) +
> + ... datetime.
> + ... year_tick += 1
> + ... form = {
> + ... 'lock_timestamp': now,
> + ... 'alt': None,
> + ... 'msgset_1': None,
> + ... 'msgset_
> + ... 'msgset_
> + ... 'msgset_
> + ... 'submit_
> + ... if force_suggestion:
> + ... form['msgset_
> + ... tm_view = create_view(
> + ... translationmessage, "+translate", form=form,
> + ... layer=Translati
> + ... tm_view.
> + ... tm_view.
> +
> + >>> def print_current_