~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
TranslationMessage
==================

Let's do some imports we will need to test this class.

    >>> from zope.component import getUtility
    >>> from lp.services.webapp.testing import verifyObject
    >>> from lp.registry.interfaces.person import IPersonSet
    >>> from lp.services.worlddata.interfaces.language import ILanguageSet
    >>> from lp.translations.interfaces.translationmessage import (
    ...     ITranslationMessage)
    >>> from lp.translations.interfaces.translator import ITranslatorSet
    >>> from lp.translations.model.pofile import DummyPOFile

    >>> login('carlos@canonical.com')
    >>> pofile_es = factory.makePOFile(language_code='es')
    >>> potemplate = pofile_es.potemplate
    >>> potmsgset = factory.makePOTMsgSet(potemplate=potemplate)

This class links the translations submitted by a translator with the
associated POFile and POTMsgSet.  TranslationMessage and
DummyTranslationMessage both implement ITranslationMessage:

    >>> translationmessage = factory.makeCurrentTranslationMessage(
    ...     potmsgset=potmsgset, pofile=pofile_es)
    >>> verifyObject(ITranslationMessage, translationmessage)
    True

    >>> dummy_message = potmsgset.getCurrentTranslationMessageOrDummy(
    ...     factory.makePOFile('xh'))
    >>> verifyObject(ITranslationMessage, dummy_message)
    True


plural_forms
------------

This property returns a number of plural forms needed for a
TranslationMessage to be 'complete', i.e. contain all the necessary
translations.

We can look at a POTMsgSet with no plural forms:

    >>> print potmsgset.plural_text
    None

Any TranslationMessage for such a POTMsgSet returns a single plural form in
the translation, no matter the number of plural forms defined for the
language:

    >>> serbian = getUtility(ILanguageSet)['sr']
    >>> serbian.pluralforms
    3
    >>> current_sr = potmsgset.getCurrentTranslationMessageOrDummy(
    ...     DummyPOFile(potemplate, serbian))
    >>> current_sr.plural_forms
    1

    >>> divehi = getUtility(ILanguageSet)['dv']
    >>> print divehi.pluralforms
    None
    >>> current_dv = potmsgset.getCurrentTranslationMessageOrDummy(
    ...     DummyPOFile(potemplate, divehi))
    >>> current_dv.plural_forms
    1

For any POTMsgSet using plural forms, we get a defined number of plural
forms per language (3 for Serbian, as specified in the language).

    >>> potmsgset_plural = factory.makePOTMsgSet(
    ...     potemplate=potemplate, singular=u"singular", plural=u"plural")

    >>> potmsgset_plural.plural_text
    u'plural'
    >>> serbian.pluralforms
    3
    >>> current_sr = potmsgset_plural.getCurrentTranslationMessageOrDummy(
    ...     DummyPOFile(potemplate, serbian))
    >>> current_sr.plural_forms
    3

In case the language doesn't have number of plural forms defined, we return
a default of 2, which is the most common number of plural forms:

    >>> print divehi.pluralforms
    None
    >>> current_dv = potmsgset_plural.getCurrentTranslationMessageOrDummy(
    ...     DummyPOFile(potemplate, divehi))
    >>> current_dv.plural_forms
    2


isHidden
--------

This method tells if a TranslationMessage is actually shown in the
web translation interface or not.

We have to commit transaction for every message update so we end up
with different timestamps on messages.

    >>> import transaction

We are working with a product with translations being restricted to
a single translation group.

    >>> productseries = potemplate.productseries
    >>> product = productseries.product
    >>> product.translationgroup = factory.makeTranslationGroup(product.owner)

    >>> from lp.translations.enums import TranslationPermission
    >>> product.translationpermission = TranslationPermission.STRUCTURED

The only Serbian reviewer in this translation group is 'name16' user.

    >>> foobar = getUtility(IPersonSet).getByName('name16')
    >>> sr_translation_reviewer = getUtility(ITranslatorSet).new(
    ...     product.translationgroup, serbian, foobar)

No Privileges Person is going to work on Serbian (sr) translation, with
the new PO file.

    >>> pofile_sr = potemplate.newPOFile('sr')
    >>> potmsgset = factory.makePOTMsgSet(potemplate=potemplate,
    ...     singular=u'evolution addressbook')

No Privileges Person can only submit a suggestion, which will not be
hidden.

    >>> nopriv = getUtility(IPersonSet).getByName('no-priv')
    >>> login('no-priv@canonical.com')

    >>> new_suggestion = potmsgset.submitSuggestion(
    ...     pofile_sr, nopriv, {0: u'suggestion'})
    >>> transaction.commit()
    >>> new_suggestion.isHidden(pofile_sr)
    False

'foobar' is a privileged translator that will do the updates.

    >>> login('foo.bar@canonical.com')

An imported translation is not hidden when submitted.

    >>> imported_translation = factory.makeCurrentTranslationMessage(
    ...     pofile_sr, potmsgset, foobar, current_other=True,
    ...     translations={ 0: 'imported' })
    >>> transaction.commit()
    >>> imported_translation.isHidden(pofile_sr)
    False

A previous suggestion is now hidden.

    >>> new_suggestion.isHidden(pofile_sr)
    True

A newly submitted non-imported translation is not hidden either.

    >>> current_translation = factory.makeCurrentTranslationMessage(
    ...     pofile_sr, potmsgset, foobar, current_other=False,
    ...     translations={ 0: 'current' })
    >>> transaction.commit()
    >>> current_translation.isHidden(pofile_sr)
    False

However, previous imported translation is not hidden yet.

    >>> imported_translation.isHidden(pofile_sr)
    False

If a new current translation is submitted, the old one is hidden.

    >>> new_current_translation = factory.makeCurrentTranslationMessage(
    ...     pofile_sr, potmsgset, foobar, current_other=False,
    ...     translations={ 0 : 'new' })
    >>> transaction.commit()
    >>> new_current_translation.isHidden(pofile_sr)
    False
    >>> current_translation.isHidden(pofile_sr)
    True

    >>> new_current_translation.isHidden(pofile_sr)
    False
    >>> imported_translation.isHidden(pofile_sr)
    False

If a non-privileged user submits another suggestion, it's not hidden,
and last current translation is not hidden either.

    >>> nopriv = getUtility(IPersonSet).getByName('no-priv')
    >>> login('no-priv@canonical.com')

    >>> another_suggestion = potmsgset.submitSuggestion(
    ...     pofile_sr, nopriv, {0: u'another suggestion'})
    >>> transaction.commit()
    >>> another_suggestion.isHidden(pofile_sr)
    False
    >>> new_current_translation.isHidden(pofile_sr)
    False


translations & all_msgstrs
--------------------------

The translations attribute is a list containing all translation strings
for the message, up to and including the last plural form it can have.

For a regular single-form message, that's always one.

    >>> login('foo.bar@canonical.com')
    >>> message = potmsgset.getCurrentTranslation(
    ...     potemplate, serbian, potemplate.translation_side)
    >>> message.translations
    [u'new']

If the message has no actual translation, the translations attribute
contains just a None.

    >>> empty_message = potmsgset.submitSuggestion(
    ...     pofile_sr, foobar, {})
    >>> empty_message.translations
    [None]

For a message with plurals, it's the POFile's number of plural forms.

    >>> spanish = getUtility(ILanguageSet)['es']
    >>> plural_potmsgset = factory.makePOTMsgSet(potemplate=potemplate,
    ...                                          singular=u"%d contact",
    ...                                          plural=u"%d contacts")
    >>> plural_message = factory.makeCurrentTranslationMessage(
    ...     potmsgset=plural_potmsgset, pofile=pofile_es,
    ...     translations=[u'%d contacto', u'%d contactos'])
    >>> plural_message.translations
    [u'%d contacto', u'%d contactos']

If the message does not translate all those forms, we get None entries
in the list.

    >>> empty_message = plural_potmsgset.submitSuggestion(
    ...     pofile_sr, foobar, {})
    >>> empty_message.translations
    [None, None, None]

The all_msgstrs attribute is simpler.  It gives us the full list of
translations for all supported plural forms, even if they are None.
These are POTranslation references, not strings.

    >>> for translation in message.all_msgstrs:
    ...     if translation is None:
    ...         print 'None'
    ...     else:
    ...         print translation.translation
    new
    None
    None
    None
    None
    None


Composing SQL involving plural forms
------------------------------------

SQL Queries involving the TranslationMessage.msgstr* attributes often
get repetitive.  We have some helper functions to make it easier on the
eyes.

    >>> from lp.translations.model.translationmessage import (
    ...     make_plurals_fragment, make_plurals_sql_fragment)

The helper function make_plurals_fragment repeats a fragment of text
for the number of plural forms we support (starting at zero).

    >>> print make_plurals_fragment("x%(form)dx", ", ")
    x0x,
    x1x,
    x2x,
    x3x,
    x4x,
    x5x

Composing text like this happens most in WHERE clauses of SQL queries.
The make_plurals_sql_fragment helper adds some parentheses and spaces
where you might otherwise forget them--or want to.

    >>> print make_plurals_sql_fragment("msgstr%(form)d IS NOT NULL")
    (msgstr0 IS NOT NULL) AND
    (msgstr1 IS NOT NULL) AND
    (msgstr2 IS NOT NULL) AND
    (msgstr3 IS NOT NULL) AND
    (msgstr4 IS NOT NULL) AND
    (msgstr5 IS NOT NULL)

The sub-clauses don't have to be tied together with AND:

    >>> print make_plurals_sql_fragment("msgstr%(form)d IS NULL", "OR")
    (msgstr0 IS NULL) OR
    (msgstr1 IS NULL) OR
    (msgstr2 IS NULL) OR
    (msgstr3 IS NULL) OR
    (msgstr4 IS NULL) OR
    (msgstr5 IS NULL)