Merge lp:~henninge/launchpad/recife-bug-611674-convert-imports into lp:~launchpad/launchpad/recife
- recife-bug-611674-convert-imports
- Merge into recife
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Данило Шеган | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 9172 | ||||
Proposed branch: | lp:~henninge/launchpad/recife-bug-611674-convert-imports | ||||
Merge into: | lp:~launchpad/launchpad/recife | ||||
Diff against target: |
595 lines (+321/-83) 8 files modified
lib/lp/translations/doc/poimport-script.txt (+2/-2) lib/lp/translations/doc/rosetta-karma.txt (+5/-4) lib/lp/translations/model/pofile.py (+3/-5) lib/lp/translations/stories/translations/30-rosetta-pofile-translation-gettext-error.txt (+12/-20) lib/lp/translations/utilities/sanitize.py (+2/-0) lib/lp/translations/utilities/tests/test_file_importer.py (+151/-3) lib/lp/translations/utilities/tests/test_sanitize.py (+14/-0) lib/lp/translations/utilities/translation_import.py (+132/-49) |
||||
To merge this branch: | bzr merge lp:~henninge/launchpad/recife-bug-611674-convert-imports | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Данило Шеган (community) | Approve | ||
Review via email: mp+37108@code.launchpad.net |
Commit message
Description of the change
Bug 611674
==========
Although the bug does not say so, this branch is about converting the import code to the new models and use the new methods for adding translations and marking them as current.
The main change is in FileImporter.
1. Add the new translation as a suggestion
2. Try to validate it
3. Approve the suggestion if it validates and has no conflicts.
A large part of the diff is made up of the new property "FileImporter.
Implementation details
-------
rosetta-karma.txt:
Because of the new scheme, import karma is now awarded for adding suggestions, not for approving them. Slightly different but not much.
30-rosetta-
I don't know why gettextpo mentions "msgid_plural" nowadays. This code will pass for both "msgid" and "msgid_plural".
sanitize.py, test_sanitize.py:
A test failed because a language returned "None" for pluralforms. I am not sure if that is a bug in itself.
translation_
The new methods translations_
The flow in storeStoreTrans
Tests
-----
I ran all lp.translations tests to find breakage but I think something like this would cover most of the import code:
bin/test -vvcm lp.translations -t poimport.txt -t poimport-script.txt -t test_file_importer -t test_sanitize -t stories.
Lint
----
Lint complains about bad indention in 30-rosetta-
Henning Eggers (henninge) wrote : | # |
> > === modified file 'lib/lp/
> > --- lib/lp/
> > +0000
> > +++ lib/lp/
> > @@ -282,9 +282,9 @@
> >
> > >>> from lp.services.
> > >>> esperanto = getUtility(
> > - >>> foos = potemplate['Foo %s'].getLocalTr
> > + >>> suggestions = potemplate['Foo %
> > s'].getLocalTra
> > ... potemplate, esperanto)
> > - >>> sorted(
> > + >>> sorted(
> > [u'Bar', u'Bars']
>
> I don't really like "sugg" much better than "foo" (though, "suggestions"
> instead of "foos" IS MUCH better :). How about "suggestion"?
Yeah, I didn't think it would fit. It does.
>
> > === modified file 'lib/lp/
> > --- lib/lp/
> > +++ lib/lp/
> > @@ -181,6 +181,8 @@
> >
> > # Expected plural forms should all exist and empty translations should
> > # be normalized to None.
> > + if pluralforms is None:
> > + pluralforms = 2
> > for pluralform in range(pluralforms):
> > if pluralform not in sanitized_
> > sanitized_
>
> What's the reasoning for this? Why not default to 1 instead (i.e.
> assume it's a message with no plurals at all)?
That's what Language does. I could remove it here and use language.
Also, there is something else I was wondering about. The function will fill in the missing plural forms with None, regardless of whether there is a msgid_plural or not. I don't remember if that is intended behavior. It does no harm but seems like a waste.
>
> > === modified file
> 'lib/lp/
> ...
> > +class FileImporterSha
> > + """Class test for the sharing operation of the FileImporter base
> class."""
> > + layer = LaunchpadZopele
> > +
> > + UPSTREAM = 0
> > + UBUNTU = 1
> > + LANGCODE = 'eo'
>
> I know this is how we usually used to do stuff, but today we should be
> able to also make languages and not depend on existing ones in the
> sampledata. For instance, if you really want to keep using 'eo', I'd
> suggest you do factory.
No, I am not intend on this. Actually I defined it here so that it does not appear in the code itself. Changed it to use a constructed Language.
>
> > + POFILE = dedent("""\
> > + msgid ""
> > + msgstr ""
> > + "PO-Revision-Date: 2005-05-03 20:41+0100\\n"
> > + "Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
> > + "Content-Type: text/plain; charset=UTF-8\\n"
> > + "X-Launchpad-
> > +
> > + msgid "Thank You"
> > + msgstr "Dankon"
> > + ...
Henning Eggers (henninge) wrote : | # |
Incremental diff.
1 | === modified file 'lib/lp/translations/doc/poimport-script.txt' |
2 | --- lib/lp/translations/doc/poimport-script.txt 2010-09-29 11:53:45 +0000 |
3 | +++ lib/lp/translations/doc/poimport-script.txt 2010-10-01 10:15:02 +0000 |
4 | @@ -284,7 +284,7 @@ |
5 | >>> esperanto = getUtility(ILanguageSet).getLanguageByCode('eo') |
6 | >>> suggestions = potemplate['Foo %s'].getLocalTranslationMessages( |
7 | ... potemplate, esperanto) |
8 | - >>> sorted([sugg.msgstr0.translation for sugg in suggestions]) |
9 | + >>> sorted([suggestion.msgstr0.translation for suggestion in suggestions]) |
10 | [u'Bar', u'Bars'] |
11 | |
12 | Since this last upload was not the upstream one, however, its credits |
13 | |
14 | === modified file 'lib/lp/translations/utilities/tests/test_file_importer.py' |
15 | --- lib/lp/translations/utilities/tests/test_file_importer.py 2010-09-30 08:51:21 +0000 |
16 | +++ lib/lp/translations/utilities/tests/test_file_importer.py 2010-10-01 13:56:41 +0000 |
17 | @@ -582,7 +582,6 @@ |
18 | |
19 | UPSTREAM = 0 |
20 | UBUNTU = 1 |
21 | - LANGCODE = 'eo' |
22 | |
23 | POFILE = dedent("""\ |
24 | msgid "" |
25 | @@ -593,13 +592,14 @@ |
26 | "X-Launchpad-Export-Date: 2009-05-14 08:54+0000\\n" |
27 | |
28 | msgid "Thank You" |
29 | - msgstr "Dankon" |
30 | + msgstr "Translation" |
31 | """) |
32 | |
33 | def setUp(self): |
34 | super(FileImporterSharingTest, self).setUp() |
35 | # Create the upstream series and template with a translator. |
36 | - self.translator = self.factory.makeTranslator(self.LANGCODE) |
37 | + self.language = self.factory.makeLanguage() |
38 | + self.translator = self.factory.makeTranslator(self.language.code) |
39 | self.upstream_productseries = self.factory.makeProductSeries() |
40 | self.upstream_productseries.product.translationgroup = ( |
41 | self.translator.translationgroup) |
42 | @@ -608,12 +608,12 @@ |
43 | self.upstream_template = self.factory.makePOTemplate( |
44 | productseries=self.upstream_productseries) |
45 | |
46 | - def _makeEntry(self, side, from_upstream=False, uploader=None): |
47 | + def _makeImportEntry(self, side, from_upstream=False, uploader=None, |
48 | + no_upstream=False): |
49 | if side == self.UPSTREAM: |
50 | potemplate = self.upstream_template |
51 | else: |
52 | - # Create a template in a source package and link the source |
53 | - # package to the upstream series to enable sharing. |
54 | + # Create a template in a source package. |
55 | ubuntu = getUtility(ILaunchpadCelebrities).ubuntu |
56 | distroseries = self.factory.makeDistroSeries(distribution=ubuntu) |
57 | ubuntu.translation_focus = distroseries |
58 | @@ -622,19 +622,24 @@ |
59 | distroseries=distroseries, |
60 | sourcepackagename=sourcepackagename, |
61 | name=self.upstream_template.name) |
62 | - self.factory.makeSourcePackagePublishingHistory( |
63 | - sourcepackagename=sourcepackagename, |
64 | - distroseries=distroseries) |
65 | - sourcepackage = distroseries.getSourcePackage(sourcepackagename) |
66 | - sourcepackage.setPackaging( |
67 | - self.upstream_productseries, self.factory.makePerson()) |
68 | + if not no_upstream: |
69 | + # Link the source package to the upstream series to |
70 | + # enable sharing. |
71 | + self.factory.makeSourcePackagePublishingHistory( |
72 | + sourcepackagename=sourcepackagename, |
73 | + distroseries=distroseries) |
74 | + sourcepackage = distroseries.getSourcePackage( |
75 | + sourcepackagename) |
76 | + sourcepackage.setPackaging( |
77 | + self.upstream_productseries, self.factory.makePerson()) |
78 | pofile = self.factory.makePOFile( |
79 | - self.LANGCODE, potemplate=potemplate, create_sharing=True) |
80 | + self.language.code, potemplate=potemplate, create_sharing=True) |
81 | entry = self.factory.makeTranslationImportQueueEntry( |
82 | potemplate=potemplate, from_upstream=from_upstream, |
83 | uploader=uploader, content=self.POFILE) |
84 | entry.potemplate = potemplate |
85 | entry.pofile = pofile |
86 | + # The uploaded file is only created in the librarian by a commit. |
87 | transaction.commit() |
88 | return entry |
89 | |
90 | @@ -642,7 +647,7 @@ |
91 | # Sanity check that the translator has the right permissions but |
92 | # others don't. |
93 | pofile = self.factory.makePOFile( |
94 | - self.LANGCODE, potemplate=self.upstream_template) |
95 | + self.language.code, potemplate=self.upstream_template) |
96 | self.assertFalse( |
97 | pofile.canEditTranslations(self.factory.makePerson())) |
98 | self.assertTrue( |
99 | @@ -650,7 +655,7 @@ |
100 | |
101 | def test_templates_are_sharing(self): |
102 | # Sharing between upstream and Ubuntu was set up correctly. |
103 | - entry = self._makeEntry(self.UBUNTU) |
104 | + entry = self._makeImportEntry(self.UBUNTU) |
105 | subset = getUtility(IPOTemplateSet).getSharingSubset( |
106 | distribution=entry.distroseries.distribution, |
107 | sourcepackagename=entry.sourcepackagename) |
108 | @@ -660,7 +665,7 @@ |
109 | |
110 | def test_share_with_other_side_upstream(self): |
111 | # An upstream queue entry will be shared with ubuntu. |
112 | - entry = self._makeEntry(self.UPSTREAM) |
113 | + entry = self._makeImportEntry(self.UPSTREAM) |
114 | importer = POFileImporter( |
115 | entry, importers[TranslationFileFormat.PO], None) |
116 | self.assertTrue( |
117 | @@ -669,7 +674,16 @@ |
118 | |
119 | def test_share_with_other_side_ubuntu(self): |
120 | # An ubuntu queue entry will not be shared with upstream. |
121 | - entry = self._makeEntry(self.UBUNTU) |
122 | + entry = self._makeImportEntry(self.UBUNTU) |
123 | + importer = POFileImporter( |
124 | + entry, importers[TranslationFileFormat.PO], None) |
125 | + self.assertFalse( |
126 | + importer.share_with_other_side, |
127 | + "Ubuntu import should not share with upstream.") |
128 | + |
129 | + def test_share_with_other_side_ubuntu_no_upstream(self): |
130 | + # An ubuntu queue entry cannot share with a non-existent upstream. |
131 | + entry = self._makeImportEntry(self.UBUNTU, no_upstream=True) |
132 | importer = POFileImporter( |
133 | entry, importers[TranslationFileFormat.PO], None) |
134 | self.assertFalse( |
135 | @@ -679,7 +693,7 @@ |
136 | def test_share_with_other_side_ubuntu_from_package(self): |
137 | # An ubuntu queue entry that is imported from an upstream package |
138 | # will be shared with upstream. |
139 | - entry = self._makeEntry(self.UBUNTU, from_upstream=True) |
140 | + entry = self._makeImportEntry(self.UBUNTU, from_upstream=True) |
141 | importer = POFileImporter( |
142 | entry, importers[TranslationFileFormat.PO], None) |
143 | self.assertTrue( |
144 | @@ -689,7 +703,7 @@ |
145 | def test_share_with_other_side_ubuntu_uploader_upstream_translator(self): |
146 | # If the uploader in ubuntu has rights on upstream as well, the |
147 | # translations are shared. |
148 | - entry = self._makeEntry( |
149 | + entry = self._makeImportEntry( |
150 | self.UBUNTU, uploader=self.translator.translator) |
151 | importer = POFileImporter( |
152 | entry, importers[TranslationFileFormat.PO], None) |
153 | |
154 | === modified file 'lib/lp/translations/utilities/translation_import.py' |
155 | --- lib/lp/translations/utilities/translation_import.py 2010-09-30 08:51:21 +0000 |
156 | +++ lib/lp/translations/utilities/translation_import.py 2010-10-01 14:27:37 +0000 |
157 | @@ -490,7 +490,11 @@ |
158 | |
159 | @cachedproperty |
160 | def translations_are_msgids(self): |
161 | - """Are symbolic msgids being used and these are the real ones?""" |
162 | + """Are these English strings instead of translations? |
163 | + |
164 | + If this template uses symbolic message ids, the English POFile |
165 | + will contain the English original texts that correspond to the |
166 | + symbols.""" |
167 | return ( |
168 | self.importer.uses_source_string_msgids and |
169 | self.pofile.language.code == 'en') |
170 | @@ -559,7 +563,6 @@ |
171 | return None |
172 | |
173 | no_translations = ( |
174 | - message_data.translations == [] or |
175 | message_data.translations is None or |
176 | not any(message_data.translations)) |
177 | if no_translations: |
Данило Шеган (danilo) wrote : | # |
У пет, 01. 10 2010. у 14:43 +0000, Henning Eggers пише:
> That's what Language does. I could remove it here and use
> language.
> something else I was wondering about. The function will fill in the
> missing plural forms with None, regardless of whether there is a
> msgid_plural or not. I don't remember if that is intended behavior. It
> does no harm but seems like a waste.
Let's keep it simple for now and not touch anything. It definitely
sounds a bit wasteful, but it's not a big deal.
> > I know this is how we usually used to do stuff, but today we should be
> > able to also make languages and not depend on existing ones in the
> > sampledata. For instance, if you really want to keep using 'eo', I'd
> > suggest you do factory.
>
> No, I am not intend on this. Actually I defined it here so that it does
> not appear in the code itself. Changed it to use a constructed
> Language.
Cool, thanks.
> > > + if side == self.UPSTREAM:
> > > + potemplate = self.upstream_
> > > + else:
> > > + # Create a template in a source package and link the source
> > > + # package to the upstream series to enable sharing.
> > > + ubuntu = getUtility(
> > > + distroseries =
> > self.factory.
> > > + ubuntu.
> > > + sourcepackagename = self.factory.
> > > + potemplate = self.factory.
> > > + distroseries=
> > > + sourcepackagena
> > > + name=self.
> > > + self.factory.
> > > + sourcepackagena
> > > + distroseries=
> >
> > Why is this necessary? Is it not possible to reach a sourcepackage
> > without it?
>
> For some reason I cannot use sourcepackage.
Right, it's probably useful knowledge worth sharing across the
translations team at least. Knowing the background would be even better
because then we could reconstruct it in the future :)
> >
> > > + return entry
> > > +
> > > + def test_translator
> >
> > s/perSmissions/
You seem to have forgotten this bit.
> >
> > > + def test_templates_
> > > + # Sharing between upstream and Ubuntu was set up correctly.
> > > + entry = self._makeEntry
> > > + subset = getUtility(
> > > + distribution=
> > > + sourcepackagena
> > > + self.assertCont
> > > + [entry.potemplate, self.upstream_
> > > + list(subset.
> >
> > This looks a lot like a test for the code that's not really here,
> > doesn't it? :)
> >
> > Or, is it basicall...
Henning Eggers (henninge) wrote : | # |
Am 05.10.2010 13:34, schrieb Данило Шеган:
>>>> + self.factory.
>>>> + sourcepackagena
>>>> + distroseries=
>>>
>>> Why is this necessary? Is it not possible to reach a sourcepackage
>>> without it?
>>
>> For some reason I cannot use sourcepackage.
>
> Right, it's probably useful knowledge worth sharing across the
> translations team at least. Knowing the background would be even better
> because then we could reconstruct it in the future :)
Hm, should I put a card in the backlog to research that?
>
>>>
>>>> + return entry
>>>> +
>>>> + def test_translator
>>>
>>> s/perSmissions/
>
> You seem to have forgotten this bit.
I did, thanks. ;)
> It'd still be nice to name the test so it reflects this: eg.
> "test_makeImpor
Renamed.
>
>>> Cool stuff. It's amazing how tests can be so simple for something this
>>> intricate. :)
>>
>> I see the smiley but I am not sure if you are making fun of me or not ... ;-/
>
> No, it's a smiley of happiness. Also a credit to your "factor out stuff
> that is not being tested" (your _makeImportEntry method).
Thank you. :-)
>>>> + @cachedproperty
>>>> + def translations_
>>>> + """Are symbolic msgids being used and these are the real ones?"""
>>>
>>> This sentence is a bit confusing. If I had no idea about what this is
>>> about, I'd have even less idea about it. :)
>>
>> OK, I tried better. ;-)
>
> I think "text" is a non-countable noun, so plural "texts" sounds weird:
It can be both. In this case it is countable
>>> This is actually very interesting, and I am happy to see that you
>>> extracted it out. It actually points out that the naming for
>>> "from_upstream" is not really perfect: it should probably be
>>> "from_maintainer" or "from_trusted" or something.
>>
>> Yes, Jeroen and I had talked about that, too. I think I like from_maintainer most.
>
> Let's add a card for that then.
Done.
>>> Oh, I just read up on any() :) It seems you can drop the comparison to
>>> the empty list as well.
>>
>> Good catch! I was a bit worried about using "any" because it tests for
>> "False" but we are looking for empty strings and "None". Although
>> these are by definition "False", too, our coding standards always
>> forbade implicit bool checks like these.
>
> That was kind of my worry as well, but I guess there's no harm since the
> intent is clear enough.
I put it on the reviewer meeting agenda.
>
>>> With the new model, there will be no way to know if a translation
>>> credits message didn't validate, but is the actual one from upstream.
>>> Something to think about.
>>
>> Hm, we could still validate and mark the message accordingly, even if
>> it is used, right? I am not sure, though, if translators expect us to
>> catch bugs like that. Maybe we should discard c-format flags on
>> translation credits POTMsgSets altogether?
>
> Definitely worth thinking about some more. How about you file a bug
> about this to look into after all the "re...
1 | === modified file 'lib/lp/translations/utilities/tests/test_file_importer.py' |
2 | --- lib/lp/translations/utilities/tests/test_file_importer.py 2010-10-05 17:06:23 +0000 |
3 | +++ lib/lp/translations/utilities/tests/test_file_importer.py 2010-10-06 09:40:23 +0000 |
4 | @@ -643,7 +643,7 @@ |
5 | transaction.commit() |
6 | return entry |
7 | |
8 | - def test_translator_persmissions(self): |
9 | + def test_translator_permissions(self): |
10 | # Sanity check that the translator has the right permissions but |
11 | # others don't. |
12 | pofile = self.factory.makePOFile( |
13 | @@ -653,7 +653,7 @@ |
14 | self.assertTrue( |
15 | pofile.canEditTranslations(self.translator.translator)) |
16 | |
17 | - def test_templates_are_sharing(self): |
18 | + def test_makeImportEntry_templates_are_sharing(self): |
19 | # Sharing between upstream and Ubuntu was set up correctly. |
20 | entry = self._makeImportEntry(self.UBUNTU) |
21 | subset = getUtility(IPOTemplateSet).getSharingSubset( |
Preview Diff
1 | === modified file 'lib/lp/translations/doc/poimport-script.txt' |
2 | --- lib/lp/translations/doc/poimport-script.txt 2010-09-21 16:19:47 +0000 |
3 | +++ lib/lp/translations/doc/poimport-script.txt 2010-10-06 11:07:49 +0000 |
4 | @@ -282,9 +282,9 @@ |
5 | |
6 | >>> from lp.services.worlddata.interfaces.language import ILanguageSet |
7 | >>> esperanto = getUtility(ILanguageSet).getLanguageByCode('eo') |
8 | - >>> foos = potemplate['Foo %s'].getLocalTranslationMessages( |
9 | + >>> suggestions = potemplate['Foo %s'].getLocalTranslationMessages( |
10 | ... potemplate, esperanto) |
11 | - >>> sorted([foo.msgstr0.translation for foo in foos]) |
12 | + >>> sorted([suggestion.msgstr0.translation for suggestion in suggestions]) |
13 | [u'Bar', u'Bars'] |
14 | |
15 | Since this last upload was not the upstream one, however, its credits |
16 | |
17 | === modified file 'lib/lp/translations/doc/rosetta-karma.txt' |
18 | --- lib/lp/translations/doc/rosetta-karma.txt 2010-08-04 11:00:51 +0000 |
19 | +++ lib/lp/translations/doc/rosetta-karma.txt 2010-10-06 11:07:49 +0000 |
20 | @@ -221,14 +221,14 @@ |
21 | Tell the PO file to import from the file data it has. The user has rights |
22 | to edit translations directly, so his suggestion is approved directly. |
23 | Even when the user is also a reviwer, he should not get karma for reviewing |
24 | -his own translations. IOW, he'll only get karma for his suggestion being |
25 | -approved. |
26 | +or approving his own translations. IOW, he'll only get karma for submitting |
27 | +his suggestion. |
28 | |
29 | >>> potemplate = POTemplate.get(1) |
30 | >>> entry = translation_import_queue[entry_id] |
31 | >>> pofile = potemplate.getPOFileByLang('es') |
32 | >>> (subject, message) = pofile.importFromQueue(entry) |
33 | - Karma added: action=translationsuggestionapproved, product=evolution |
34 | + Karma added: action=translationsuggestionadded, product=evolution |
35 | >>> transaction.commit() |
36 | |
37 | Let's try the case when a file is uploaded, but no translation is changed. |
38 | @@ -384,7 +384,8 @@ |
39 | We do the update and see the karma being assigned. |
40 | |
41 | >>> status = potemplate_view.initialize() |
42 | - Karma added: action=translationtemplatedescriptionchanged, product=evolution |
43 | + Karma added: |
44 | + action=translationtemplatedescriptionchanged, product=evolution |
45 | |
46 | And the new one is: |
47 | |
48 | |
49 | === modified file 'lib/lp/translations/model/pofile.py' |
50 | --- lib/lp/translations/model/pofile.py 2010-09-06 10:40:54 +0000 |
51 | +++ lib/lp/translations/model/pofile.py 2010-10-06 11:07:49 +0000 |
52 | @@ -538,7 +538,8 @@ |
53 | applicable_template = Coalesce( |
54 | TranslationMessage.potemplateID, self.potemplate.id) |
55 | clauses = [ |
56 | - TranslationTemplateItem.potmsgsetID == TranslationMessage.potmsgsetID, |
57 | + TranslationTemplateItem.potmsgsetID == ( |
58 | + TranslationMessage.potmsgsetID), |
59 | TranslationTemplateItem.potemplate == self.potemplate, |
60 | TranslationMessage.language == self.language, |
61 | applicable_template == self.potemplate.id, |
62 | @@ -1108,8 +1109,7 @@ |
63 | |
64 | # Prepare the mail notification. |
65 | msgsets_imported = self.getTranslationMessages( |
66 | - TranslationMessage.was_obsolete_in_last_import == False |
67 | - ).count() |
68 | + TranslationMessage.was_obsolete_in_last_import == False).count() |
69 | |
70 | replacements = collect_import_info(entry_to_import, self, warnings) |
71 | replacements.update({ |
72 | @@ -1179,8 +1179,6 @@ |
73 | # Assign karma to the importer if this is not an automatic import |
74 | # (all automatic imports come from the rosetta expert user) and |
75 | # comes from upstream. |
76 | - celebs = getUtility(ILaunchpadCelebrities) |
77 | - rosetta_experts = celebs.rosetta_experts |
78 | if (entry_to_import.from_upstream and |
79 | entry_to_import.importer.id != rosetta_experts.id): |
80 | entry_to_import.importer.assignKarma( |
81 | |
82 | === modified file 'lib/lp/translations/stories/translations/30-rosetta-pofile-translation-gettext-error.txt' |
83 | --- lib/lp/translations/stories/translations/30-rosetta-pofile-translation-gettext-error.txt 2010-09-20 05:32:10 +0000 |
84 | +++ lib/lp/translations/stories/translations/30-rosetta-pofile-translation-gettext-error.txt 2010-10-06 11:07:49 +0000 |
85 | @@ -38,38 +38,30 @@ |
86 | >>> user_browser.getControl( |
87 | ... name='msgset_150_es_translation_1_radiobutton').value = [ |
88 | ... 'msgset_150_es_translation_1_new'] |
89 | - >>> user_browser.getControl(name='msgset_150_es_translation_1_new').value = ( |
90 | + >>> user_browser.getControl( |
91 | + ... name='msgset_150_es_translation_1_new').value = ( |
92 | ... u'Found %s invalid files') |
93 | >>> user_browser.getControl(name='submit_translations').click() |
94 | |
95 | Because of the error, we're still in on the same page. |
96 | |
97 | >>> print user_browser.url |
98 | - http://translations.launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/+translate?start=20 |
99 | + http://.../hoary/+source/evo.../+pots/evo...-2.2/es/+translate?start=20 |
100 | |
101 | And we can see the error. |
102 | |
103 | >>> for tag in find_tags_by_class(user_browser.contents, 'error'): |
104 | - ... print tag |
105 | - <div class="error message">There is an error in a translation you provided. |
106 | - Please correct it before continuing.</div> |
107 | - <tr class="error translation"> |
108 | - <th colspan="3"> |
109 | - <strong>Error in Translation:</strong> |
110 | - </th> |
111 | - <td> |
112 | - </td><td> |
113 | - <div> |
114 | - format specifications in ... and 'msgstr[0]' for argument 1 are |
115 | - not the sameformat specifications in ... and 'msgstr[1]' for |
116 | - argument 1 are not the same |
117 | - </div> |
118 | - </td> |
119 | - </tr> |
120 | + ... print extract_text(tag) |
121 | + There is an error in a translation you provided. |
122 | + Please correct it before continuing. |
123 | + Error in Translation: |
124 | + format specifications in 'msgid...' and 'msgstr[0]' for argument 1 are |
125 | + not the sameformat specifications in 'msgid...' and 'msgstr[1]' for |
126 | + argument 1 are not the same |
127 | |
128 | The translation form got an error with the translations we wanted to store, |
129 | -and thus we still have that text as part of translations input, otherwise, they |
130 | -will be empty waiting for new suggestions/translations. |
131 | +and thus we still have that text as part of translations input, otherwise, |
132 | +they will be empty waiting for new suggestions/translations. |
133 | |
134 | >>> user_browser.getControl(name='msgset_150_es_translation_0_new').value |
135 | 'Found %s invalid file' |
136 | |
137 | === modified file 'lib/lp/translations/utilities/sanitize.py' |
138 | --- lib/lp/translations/utilities/sanitize.py 2010-08-16 09:01:35 +0000 |
139 | +++ lib/lp/translations/utilities/sanitize.py 2010-10-06 11:07:49 +0000 |
140 | @@ -181,6 +181,8 @@ |
141 | |
142 | # Expected plural forms should all exist and empty translations should |
143 | # be normalized to None. |
144 | + if pluralforms is None: |
145 | + pluralforms = 2 |
146 | for pluralform in range(pluralforms): |
147 | if pluralform not in sanitized_translations: |
148 | sanitized_translations[pluralform] = None |
149 | |
150 | === modified file 'lib/lp/translations/utilities/tests/test_file_importer.py' |
151 | --- lib/lp/translations/utilities/tests/test_file_importer.py 2010-09-30 17:57:01 +0000 |
152 | +++ lib/lp/translations/utilities/tests/test_file_importer.py 2010-10-06 11:07:49 +0000 |
153 | @@ -5,13 +5,24 @@ |
154 | |
155 | __metaclass__ = type |
156 | |
157 | +from textwrap import dedent |
158 | + |
159 | +import transaction |
160 | from zope.component import getUtility |
161 | from zope.security.proxy import removeSecurityProxy |
162 | |
163 | -from canonical.testing import ZopelessDatabaseLayer |
164 | +from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
165 | +from canonical.testing import ( |
166 | + LaunchpadZopelessLayer, |
167 | + ZopelessDatabaseLayer, |
168 | + ) |
169 | from lp.registry.interfaces.person import IPersonSet |
170 | from lp.testing import TestCaseWithFactory |
171 | from lp.testing.fakelibrarian import FakeLibrarian |
172 | +from lp.translations.interfaces.potemplate import IPOTemplateSet |
173 | +from lp.translations.interfaces.translationfileformat import ( |
174 | + TranslationFileFormat, |
175 | + ) |
176 | from lp.translations.interfaces.translationgroup import TranslationPermission |
177 | from lp.translations.interfaces.translationimporter import ( |
178 | OutdatedTranslationError, |
179 | @@ -23,6 +34,7 @@ |
180 | from lp.translations.utilities.translation_common_format import ( |
181 | TranslationMessageData) |
182 | from lp.translations.utilities.translation_import import ( |
183 | + importers, |
184 | FileImporter, |
185 | POFileImporter, |
186 | POTFileImporter, |
187 | @@ -367,11 +379,11 @@ |
188 | def test_FileImporter_importFile_ok(self): |
189 | # Test correct import operation for both |
190 | # exported and upstream files. |
191 | - importers = ( |
192 | + used_importers = ( |
193 | self._createImporterForExportedEntries(), |
194 | self._createImporterForUpstreamEntries(), |
195 | ) |
196 | - for (pot_importer, po_importer) in importers: |
197 | + for (pot_importer, po_importer) in used_importers: |
198 | # Run the import and see if PotMsgSet and TranslationMessage |
199 | # entries are correctly created in the DB. |
200 | errors, warnings = pot_importer.importFile() |
201 | @@ -562,3 +574,139 @@ |
202 | old_raw_header = pofile.header |
203 | importer = POFileImporter(queue_entry, GettextPOImporter(), None) |
204 | self.assertEqual(old_raw_header, pofile.header) |
205 | + |
206 | + |
207 | +class FileImporterSharingTest(TestCaseWithFactory): |
208 | + """Class test for the sharing operation of the FileImporter base class.""" |
209 | + layer = LaunchpadZopelessLayer |
210 | + |
211 | + UPSTREAM = 0 |
212 | + UBUNTU = 1 |
213 | + |
214 | + POFILE = dedent("""\ |
215 | + msgid "" |
216 | + msgstr "" |
217 | + "PO-Revision-Date: 2005-05-03 20:41+0100\\n" |
218 | + "Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n" |
219 | + "Content-Type: text/plain; charset=UTF-8\\n" |
220 | + "X-Launchpad-Export-Date: 2009-05-14 08:54+0000\\n" |
221 | + |
222 | + msgid "Thank You" |
223 | + msgstr "Translation" |
224 | + """) |
225 | + |
226 | + def setUp(self): |
227 | + super(FileImporterSharingTest, self).setUp() |
228 | + # Create the upstream series and template with a translator. |
229 | + self.language = self.factory.makeLanguage() |
230 | + self.translator = self.factory.makeTranslator(self.language.code) |
231 | + self.upstream_productseries = self.factory.makeProductSeries() |
232 | + self.upstream_productseries.product.translationgroup = ( |
233 | + self.translator.translationgroup) |
234 | + self.upstream_productseries.product.translationpermission = ( |
235 | + TranslationPermission.RESTRICTED) |
236 | + self.upstream_template = self.factory.makePOTemplate( |
237 | + productseries=self.upstream_productseries) |
238 | + |
239 | + def _makeImportEntry(self, side, from_upstream=False, uploader=None, |
240 | + no_upstream=False): |
241 | + if side == self.UPSTREAM: |
242 | + potemplate = self.upstream_template |
243 | + else: |
244 | + # Create a template in a source package. |
245 | + ubuntu = getUtility(ILaunchpadCelebrities).ubuntu |
246 | + distroseries = self.factory.makeDistroSeries(distribution=ubuntu) |
247 | + ubuntu.translation_focus = distroseries |
248 | + sourcepackagename = self.factory.makeSourcePackageName() |
249 | + potemplate = self.factory.makePOTemplate( |
250 | + distroseries=distroseries, |
251 | + sourcepackagename=sourcepackagename, |
252 | + name=self.upstream_template.name) |
253 | + if not no_upstream: |
254 | + # Link the source package to the upstream series to |
255 | + # enable sharing. |
256 | + self.factory.makeSourcePackagePublishingHistory( |
257 | + sourcepackagename=sourcepackagename, |
258 | + distroseries=distroseries) |
259 | + sourcepackage = distroseries.getSourcePackage( |
260 | + sourcepackagename) |
261 | + sourcepackage.setPackaging( |
262 | + self.upstream_productseries, self.factory.makePerson()) |
263 | + pofile = self.factory.makePOFile( |
264 | + self.language.code, potemplate=potemplate, create_sharing=True) |
265 | + entry = self.factory.makeTranslationImportQueueEntry( |
266 | + potemplate=potemplate, from_upstream=from_upstream, |
267 | + uploader=uploader, content=self.POFILE) |
268 | + entry.potemplate = potemplate |
269 | + entry.pofile = pofile |
270 | + # The uploaded file is only created in the librarian by a commit. |
271 | + transaction.commit() |
272 | + return entry |
273 | + |
274 | + def test_translator_permissions(self): |
275 | + # Sanity check that the translator has the right permissions but |
276 | + # others don't. |
277 | + pofile = self.factory.makePOFile( |
278 | + self.language.code, potemplate=self.upstream_template) |
279 | + self.assertFalse( |
280 | + pofile.canEditTranslations(self.factory.makePerson())) |
281 | + self.assertTrue( |
282 | + pofile.canEditTranslations(self.translator.translator)) |
283 | + |
284 | + def test_makeImportEntry_templates_are_sharing(self): |
285 | + # Sharing between upstream and Ubuntu was set up correctly. |
286 | + entry = self._makeImportEntry(self.UBUNTU) |
287 | + subset = getUtility(IPOTemplateSet).getSharingSubset( |
288 | + distribution=entry.distroseries.distribution, |
289 | + sourcepackagename=entry.sourcepackagename) |
290 | + self.assertContentEqual( |
291 | + [entry.potemplate, self.upstream_template], |
292 | + list(subset.getSharingPOTemplates(entry.potemplate.name))) |
293 | + |
294 | + def test_share_with_other_side_upstream(self): |
295 | + # An upstream queue entry will be shared with ubuntu. |
296 | + entry = self._makeImportEntry(self.UPSTREAM) |
297 | + importer = POFileImporter( |
298 | + entry, importers[TranslationFileFormat.PO], None) |
299 | + self.assertTrue( |
300 | + importer.share_with_other_side, |
301 | + "Upstream import should share with Ubuntu.") |
302 | + |
303 | + def test_share_with_other_side_ubuntu(self): |
304 | + # An ubuntu queue entry will not be shared with upstream. |
305 | + entry = self._makeImportEntry(self.UBUNTU) |
306 | + importer = POFileImporter( |
307 | + entry, importers[TranslationFileFormat.PO], None) |
308 | + self.assertFalse( |
309 | + importer.share_with_other_side, |
310 | + "Ubuntu import should not share with upstream.") |
311 | + |
312 | + def test_share_with_other_side_ubuntu_no_upstream(self): |
313 | + # An ubuntu queue entry cannot share with a non-existent upstream. |
314 | + entry = self._makeImportEntry(self.UBUNTU, no_upstream=True) |
315 | + importer = POFileImporter( |
316 | + entry, importers[TranslationFileFormat.PO], None) |
317 | + self.assertFalse( |
318 | + importer.share_with_other_side, |
319 | + "Ubuntu import should not share with upstream.") |
320 | + |
321 | + def test_share_with_other_side_ubuntu_from_package(self): |
322 | + # An ubuntu queue entry that is imported from an upstream package |
323 | + # will be shared with upstream. |
324 | + entry = self._makeImportEntry(self.UBUNTU, from_upstream=True) |
325 | + importer = POFileImporter( |
326 | + entry, importers[TranslationFileFormat.PO], None) |
327 | + self.assertTrue( |
328 | + importer.share_with_other_side, |
329 | + "Ubuntu import should share with upstream.") |
330 | + |
331 | + def test_share_with_other_side_ubuntu_uploader_upstream_translator(self): |
332 | + # If the uploader in ubuntu has rights on upstream as well, the |
333 | + # translations are shared. |
334 | + entry = self._makeImportEntry( |
335 | + self.UBUNTU, uploader=self.translator.translator) |
336 | + importer = POFileImporter( |
337 | + entry, importers[TranslationFileFormat.PO], None) |
338 | + self.assertTrue( |
339 | + importer.share_with_other_side, |
340 | + "Ubuntu import should share with upstream.") |
341 | |
342 | === modified file 'lib/lp/translations/utilities/tests/test_sanitize.py' |
343 | --- lib/lp/translations/utilities/tests/test_sanitize.py 2010-08-23 08:41:03 +0000 |
344 | +++ lib/lp/translations/utilities/tests/test_sanitize.py 2010-10-06 11:07:49 +0000 |
345 | @@ -257,6 +257,20 @@ |
346 | expected_sanitized, |
347 | sanitize_translations_from_webui(self.english, translations, 3)) |
348 | |
349 | + def test_sanitize_translations_pluralforms_none(self): |
350 | + # Some languages don't provide a plural form, so 2 is assumed. |
351 | + translations = { |
352 | + 0: u'Plural form 0 ', |
353 | + } |
354 | + expected_sanitized = { |
355 | + 0: u'Plural form 0', |
356 | + 1: None, |
357 | + } |
358 | + self.assertEqual( |
359 | + expected_sanitized, |
360 | + sanitize_translations_from_webui( |
361 | + self.english, translations, None)) |
362 | + |
363 | |
364 | def test_suite(): |
365 | return unittest.TestLoader().loadTestsFromName(__name__) |
366 | |
367 | === modified file 'lib/lp/translations/utilities/translation_import.py' |
368 | --- lib/lp/translations/utilities/translation_import.py 2010-09-06 10:40:54 +0000 |
369 | +++ lib/lp/translations/utilities/translation_import.py 2010-10-06 11:07:49 +0000 |
370 | @@ -31,6 +31,11 @@ |
371 | PersonCreationRationale, |
372 | ) |
373 | from lp.services.propertycache import cachedproperty |
374 | +from lp.translations.interfaces.potemplate import IPOTemplateSet |
375 | +from lp.translations.interfaces.side import ( |
376 | + ITranslationSideTraitsSet, |
377 | + TranslationSide, |
378 | + ) |
379 | from lp.translations.interfaces.translationexporter import ( |
380 | ITranslationExporter, |
381 | ) |
382 | @@ -45,15 +50,25 @@ |
383 | from lp.translations.interfaces.translationimportqueue import ( |
384 | RosettaImportStatus, |
385 | ) |
386 | -from lp.translations.interfaces.translationmessage import TranslationConflict |
387 | +from lp.translations.interfaces.translationmessage import ( |
388 | + RosettaTranslationOrigin, |
389 | + TranslationConflict, |
390 | + TranslationValidationStatus, |
391 | + ) |
392 | from lp.translations.interfaces.translations import TranslationConstants |
393 | from lp.translations.utilities.gettext_po_importer import GettextPOImporter |
394 | from lp.translations.utilities.kde_po_importer import KdePOImporter |
395 | from lp.translations.utilities.mozilla_xpi_importer import MozillaXpiImporter |
396 | +from lp.translations.utilities.sanitize import ( |
397 | + sanitize_translations_from_import, |
398 | + ) |
399 | from lp.translations.utilities.translation_common_format import ( |
400 | TranslationMessageData, |
401 | ) |
402 | -from lp.translations.utilities.validate import GettextValidationError |
403 | +from lp.translations.utilities.validate import ( |
404 | + GettextValidationError, |
405 | + validate_translation, |
406 | + ) |
407 | |
408 | |
409 | importers = { |
410 | @@ -445,7 +460,89 @@ |
411 | context=message.context)) |
412 | return potmsgset |
413 | |
414 | - def storeTranslationsInDatabase(self, message, potmsgset): |
415 | + @cachedproperty |
416 | + def share_with_other_side(self): |
417 | + """Returns True if translations should be shared with the other side. |
418 | + """ |
419 | + traits = getUtility( |
420 | + ITranslationSideTraitsSet).getForTemplate(self.potemplate) |
421 | + if traits.side == TranslationSide.UPSTREAM: |
422 | + return True |
423 | + # Check from_upstream. |
424 | + if self.translation_import_queue_entry.from_upstream: |
425 | + return True |
426 | + # Find the sharing POFile and check permissions. |
427 | + productseries = self.potemplate.distroseries.getSourcePackage( |
428 | + self.potemplate.sourcepackagename).productseries |
429 | + if productseries is None: |
430 | + return False |
431 | + upstream_template = getUtility(IPOTemplateSet).getSubset( |
432 | + productseries=productseries).getPOTemplateByName( |
433 | + self.potemplate.name) |
434 | + upstream_pofile = upstream_template.getPOFileByLang( |
435 | + self.pofile.language.code) |
436 | + if upstream_pofile is not None: |
437 | + uploader_person = self.translation_import_queue_entry.importer |
438 | + if upstream_pofile.canEditTranslations(uploader_person): |
439 | + return True |
440 | + # Deny the rest. |
441 | + return False |
442 | + |
443 | + @cachedproperty |
444 | + def translations_are_msgids(self): |
445 | + """Are these English strings instead of translations? |
446 | + |
447 | + If this template uses symbolic message ids, the English POFile |
448 | + will contain the English original texts that correspond to the |
449 | + symbols.""" |
450 | + return ( |
451 | + self.importer.uses_source_string_msgids and |
452 | + self.pofile.language.code == 'en') |
453 | + |
454 | + def _storeCredits(self, potmsgset, credits): |
455 | + """Store credits but only from upstream.""" |
456 | + if not self.translation_import_queue_entry.from_upstream: |
457 | + return None |
458 | + return potmsgset.setCurrentTranslation( |
459 | + self.pofile, self.last_translator, credits, |
460 | + RosettaTranslationOrigin.SCM, self.share_with_other_side) |
461 | + |
462 | + def _validateMessage(self, potmsgset, message, |
463 | + translations, message_data): |
464 | + """Validate the message and report success or failure.""" |
465 | + try: |
466 | + validate_translation( |
467 | + potmsgset.singular_text, potmsgset.plural_text, |
468 | + translations, potmsgset.flags) |
469 | + except GettextValidationError, e: |
470 | + self._addUpdateError(message_data, potmsgset, unicode(e)) |
471 | + message.validation_status = ( |
472 | + TranslationValidationStatus.UNKNOWNERROR) |
473 | + return False |
474 | + |
475 | + message.validation_status = TranslationValidationStatus.OK |
476 | + return True |
477 | + |
478 | + def _approveMessage(self, potmsgset, message, message_data): |
479 | + """Try to approve the message, return None on TranslationConflict.""" |
480 | + try: |
481 | + message.approve( |
482 | + self.pofile, self.last_translator, |
483 | + self.share_with_other_side, self.lock_timestamp) |
484 | + except TranslationConflict: |
485 | + self._addConflictError(message_data, potmsgset) |
486 | + if self.logger is not None: |
487 | + self.logger.info( |
488 | + "Conflicting updates on message %d." % potmsgset.id) |
489 | + # The message remains a suggestion. |
490 | + return None |
491 | + |
492 | + if self.translations_are_msgids: |
493 | + potmsgset.clearCachedSingularText() |
494 | + |
495 | + return message |
496 | + |
497 | + def storeTranslationsInDatabase(self, message_data, potmsgset): |
498 | """Try to store translations in the database. |
499 | |
500 | Perform check if a PO file is available and if the message has any |
501 | @@ -453,7 +550,8 @@ |
502 | is added to the list in self.errors but the translations are stored |
503 | anyway, marked as having an error. |
504 | |
505 | - :param message: The message for which translations will be stored. |
506 | + :param message_data: The message data for which translations will be |
507 | + stored. |
508 | :param potmsgset: The POTMsgSet that this message belongs to. |
509 | |
510 | :return: The updated translation_message entry or None, if no storing |
511 | @@ -464,54 +562,39 @@ |
512 | # store English strings in an IPOFile. |
513 | return None |
514 | |
515 | - if (not message.translations or |
516 | - set(message.translations) == set([u'']) or |
517 | - set(message.translations) == set([None])): |
518 | + no_translations = ( |
519 | + message_data.translations is None or |
520 | + not any(message_data.translations)) |
521 | + if no_translations: |
522 | # We don't have anything to import. |
523 | return None |
524 | |
525 | - try: |
526 | - # Do the actual import. |
527 | - translation_message = potmsgset.updateTranslation( |
528 | - self.pofile, self.last_translator, message.translations, |
529 | - self.translation_import_queue_entry.from_upstream, |
530 | - self.lock_timestamp, force_edition_rights=self.is_editor) |
531 | - except TranslationConflict: |
532 | - self._addConflictError(message, potmsgset) |
533 | - if self.logger is not None: |
534 | - self.logger.info( |
535 | - "Conflicting updates on message %d." % potmsgset.id) |
536 | - return None |
537 | - except GettextValidationError, e: |
538 | - # We got an error, so we submit the translation again but |
539 | - # this time asking to store it as a translation with |
540 | - # errors. |
541 | - |
542 | - # Add the pomsgset to the list of pomsgsets with errors. |
543 | - self._addUpdateError(message, potmsgset, unicode(e)) |
544 | - |
545 | - try: |
546 | - translation_message = potmsgset.updateTranslation( |
547 | - self.pofile, self.last_translator, message.translations, |
548 | - self.translation_import_queue_entry.from_upstream, |
549 | - self.lock_timestamp, ignore_errors=True, |
550 | - force_edition_rights=self.is_editor) |
551 | - except TranslationConflict: |
552 | - # A conflict on top of a validation error? Give up. |
553 | - # This message is cursed. |
554 | - if self.logger is not None: |
555 | - self.logger.info( |
556 | - "Conflicting updates; ignoring invalid message %d." % |
557 | - potmsgset.id) |
558 | - return None |
559 | - |
560 | - just_replaced_msgid = ( |
561 | - self.importer.uses_source_string_msgids and |
562 | - self.pofile.language.code == 'en') |
563 | - if just_replaced_msgid: |
564 | - potmsgset.clearCachedSingularText() |
565 | - |
566 | - return translation_message |
567 | + sanitized_translations = sanitize_translations_from_import( |
568 | + potmsgset.singular_text, message_data.translations, |
569 | + self.pofile.language.pluralforms) |
570 | + |
571 | + if potmsgset.is_translation_credit: |
572 | + # Translation credits cannot be added as suggestions. |
573 | + return self._storeCredits(potmsgset, sanitized_translations) |
574 | + |
575 | + # The message is first stored as a suggestion and only made |
576 | + # current if it validates. |
577 | + new_message = potmsgset.submitSuggestion( |
578 | + self.pofile, self.last_translator, sanitized_translations) |
579 | + |
580 | + validation_ok = self._validateMessage( |
581 | + potmsgset, new_message, sanitized_translations, message_data) |
582 | + if validation_ok and self.is_editor: |
583 | + return self._approveMessage(potmsgset, new_message, message_data) |
584 | + |
585 | + if self.translation_import_queue_entry.from_upstream: |
586 | + # XXX: henninge 2010-09-21: Mixed models! |
587 | + # This is mimicking the old behavior to still mark these messages |
588 | + # as "imported". Will have to be removed when |
589 | + # getPOTMsgSetsWithErrors is updated to the new model. |
590 | + new_message.makeCurrentUpstream(True) |
591 | + |
592 | + return new_message |
593 | |
594 | def importMessage(self, message): |
595 | """Import a single message. |
Hi Henning,
Thanks for tackling this. You've done a part of another card with this
job, so good job there :)
I feel it still needs a few improvements. See below.
review needs-fixing
> A large part of the diff is made up of the new property share_with_ other_side" and its tests. The property is a
> "FileImporter.
> boolean that reflects the sharing policy for translations from the
> file that is being imported. The policy depends on where the
> translations are coming from and what they are being imported into and
> on the permissions of the uploader. This was isolated to be able to
> change this policy later. (And I admit it could have warranted an
> extra branch.)
Interesting approach. We'll need to extract this even further, but
we've already got a separate card for that. You've just made that job
simpler (though, it's not going to be identical, since we'll have to
support the specifics of the web UI as well).
> === modified file 'lib/lp/ translations/ doc/poimport- script. txt' translations/ doc/poimport- script. txt 2010-09-21 16:19:47 translations/ doc/poimport- script. txt 2010-09-30 08:53:45 +0000 worlddata. interfaces. language import ILanguageSet ILanguageSet) .getLanguageByC ode('eo' ) anslationMessag es( nslationMessage s( [foo.msgstr0. translation for foo in foos]) [sugg.msgstr0. translation for sugg in suggestions])
> --- lib/lp/
> +0000
> +++ lib/lp/
> @@ -282,9 +282,9 @@
>
> >>> from lp.services.
> >>> esperanto = getUtility(
> - >>> foos = potemplate['Foo %s'].getLocalTr
> + >>> suggestions = potemplate['Foo %
> s'].getLocalTra
> ... potemplate, esperanto)
> - >>> sorted(
> + >>> sorted(
> [u'Bar', u'Bars']
I don't really like "sugg" much better than "foo" (though, "suggestions"
instead of "foos" IS MUCH better :). How about "suggestion"?
> === modified file 'lib/lp/ translations/ utilities/ sanitize. py' translations/ utilities/ sanitize. py 2010-08-16 09:01:35 +0000 translations/ utilities/ sanitize. py 2010-09-30 08:53:45 +0000 translations: translations[ pluralform] = None
> --- lib/lp/
> +++ lib/lp/
> @@ -181,6 +181,8 @@
>
> # Expected plural forms should all exist and empty translations should
> # be normalized to None.
> + if pluralforms is None:
> + pluralforms = 2
> for pluralform in range(pluralforms):
> if pluralform not in sanitized_
> sanitized_
What's the reasoning for this? Why not default to 1 instead (i.e.
assume it's a message with no plurals at all)?
> === modified file 'lib/lp/ translations/ utilities/ tests/test_ sanitize. py' translations/ utilities/ tests/test_ sanitize. py 2010-08-23 08:41:03 +0000 translations/ utilities/ tests/test_ sanitize. py 2010-09-30 08:53:45 +0000 translations_ from_webui( self.english, translations, 3)) translations_ pluralforms_ none(self) :
> --- lib/lp/
> +++ lib/lp/
> @@ -257,6 +257,20 @@
> expected_sanitized,
> sanitize_
>
> + def test_sanitize_
> + # Some languages don't provide a plural form, so 2 is assumed.
> + translations = {
> + 0: u'Plural form 0 ',
> + }
> + expected_sanitized = {
> + 0: u'Plural form 0',
> + ...