Merge lp:~jtv/launchpad/custom-language-codes into lp:launchpad
- custom-language-codes
- Merge into devel
Proposed by
Jeroen T. Vermeulen
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Abel Deuring | ||||
Approved revision: | not available | ||||
Merged at revision: | not available | ||||
Proposed branch: | lp:~jtv/launchpad/custom-language-codes | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
1255 lines (+878/-67) 21 files modified
lib/lp/registry/browser/distributionsourcepackage.py (+4/-1) lib/lp/registry/browser/product.py (+4/-2) lib/lp/registry/configure.zcml (+5/-0) lib/lp/registry/interfaces/distribution.py (+0/-7) lib/lp/registry/interfaces/product.py (+0/-7) lib/lp/registry/model/distribution.py (+0/-7) lib/lp/registry/model/distributionsourcepackage.py (+23/-2) lib/lp/registry/model/product.py (+15/-9) lib/lp/translations/browser/configure.zcml (+45/-0) lib/lp/translations/browser/customlanguagecode.py (+171/-0) lib/lp/translations/configure.zcml (+5/-0) lib/lp/translations/interfaces/customlanguagecode.py (+65/-10) lib/lp/translations/model/customlanguagecode.py (+72/-3) lib/lp/translations/model/translationimportqueue.py (+5/-4) lib/lp/translations/stories/standalone/custom-language-codes.txt (+276/-0) lib/lp/translations/templates/customlanguagecode-add.pt (+29/-0) lib/lp/translations/templates/customlanguagecode-index.pt (+46/-0) lib/lp/translations/templates/customlanguagecodes-index.pt (+80/-0) lib/lp/translations/templates/product-portlet-translatables.pt (+12/-0) lib/lp/translations/templates/sourcepackage-translations.pt (+9/-0) lib/lp/translations/tests/test_autoapproval.py (+12/-15) |
||||
To merge this branch: | bzr merge lp:~jtv/launchpad/custom-language-codes | ||||
Related bugs: |
|
||||
Related blueprints: |
UI for custom language codes
(Undefined)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Abel Deuring (community) | code | Approve | |
Martin Albisetti | ui | Pending | |
Review via email:
|
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jeroen T. Vermeulen (jtv) wrote : | # |
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Abel Deuring (adeuring) wrote : | # |
Hi Jeroen,
a nice branch, the Losas will appreciate it, I am sure.
We discuessed two minor issues on IRC (NotFound error instead of UnexpectedFormData in HasCustomlagnua
review:
Approve
(code)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/registry/browser/distributionsourcepackage.py' |
2 | --- lib/lp/registry/browser/distributionsourcepackage.py 2009-11-10 21:33:20 +0000 |
3 | +++ lib/lp/registry/browser/distributionsourcepackage.py 2009-11-19 04:05:29 +0000 |
4 | @@ -50,6 +50,8 @@ |
5 | from lp.registry.browser.packaging import PackagingDeleteView |
6 | from lp.registry.interfaces.pocket import pocketsuffix |
7 | from lp.registry.interfaces.product import IDistributionSourcePackage |
8 | +from lp.translations.browser.customlanguagecode import ( |
9 | + HasCustomLanguageCodesTraversalMixin) |
10 | |
11 | |
12 | class DistributionSourcePackageBreadcrumb(Breadcrumb): |
13 | @@ -117,7 +119,8 @@ |
14 | |
15 | |
16 | class DistributionSourcePackageNavigation(Navigation, |
17 | - BugTargetTraversalMixin, QuestionTargetTraversalMixin, |
18 | + BugTargetTraversalMixin, HasCustomLanguageCodesTraversalMixin, |
19 | + QuestionTargetTraversalMixin, |
20 | StructuralSubscriptionTargetTraversalMixin): |
21 | |
22 | usedfor = IDistributionSourcePackage |
23 | |
24 | === modified file 'lib/lp/registry/browser/product.py' |
25 | --- lib/lp/registry/browser/product.py 2009-11-11 16:57:29 +0000 |
26 | +++ lib/lp/registry/browser/product.py 2009-11-19 04:05:29 +0000 |
27 | @@ -86,6 +86,8 @@ |
28 | from lp.answers.browser.faqtarget import FAQTargetNavigationMixin |
29 | from canonical.launchpad.browser.feeds import FeedsMixin |
30 | from lp.registry.browser.productseries import get_series_branch_error |
31 | +from lp.translations.browser.customlanguagecode import ( |
32 | + HasCustomLanguageCodesTraversalMixin) |
33 | from canonical.launchpad.browser.multistep import MultiStepView, StepView |
34 | from lp.answers.browser.questiontarget import ( |
35 | QuestionTargetFacetMixin, QuestionTargetTraversalMixin) |
36 | @@ -118,8 +120,8 @@ |
37 | |
38 | class ProductNavigation( |
39 | Navigation, BugTargetTraversalMixin, |
40 | - FAQTargetNavigationMixin, QuestionTargetTraversalMixin, |
41 | - StructuralSubscriptionTargetTraversalMixin): |
42 | + FAQTargetNavigationMixin, HasCustomLanguageCodesTraversalMixin, |
43 | + QuestionTargetTraversalMixin, StructuralSubscriptionTargetTraversalMixin): |
44 | |
45 | usedfor = IProduct |
46 | |
47 | |
48 | === modified file 'lib/lp/registry/configure.zcml' |
49 | --- lib/lp/registry/configure.zcml 2009-11-15 01:05:49 +0000 |
50 | +++ lib/lp/registry/configure.zcml 2009-11-19 04:05:29 +0000 |
51 | @@ -353,6 +353,9 @@ |
52 | <class |
53 | class="lp.registry.model.distributionsourcepackage.DistributionSourcePackage"> |
54 | <allow |
55 | + interface="lp.translations.interfaces.customlanguagecode.IHasCustomLanguageCodes"/> |
56 | + |
57 | + <allow |
58 | attributes=" |
59 | distribution |
60 | development_version |
61 | @@ -1045,6 +1048,8 @@ |
62 | interface="lp.registry.interfaces.product.IProductPublic"/> |
63 | <allow |
64 | interface="lp.translations.interfaces.translationimportqueue.IHasTranslationImports"/> |
65 | + <allow |
66 | + interface="lp.translations.interfaces.customlanguagecode.IHasCustomLanguageCodes"/> |
67 | <require |
68 | permission="launchpad.Driver" |
69 | interface="lp.registry.interfaces.product.IProductDriverRestricted"/> |
70 | |
71 | === modified file 'lib/lp/registry/interfaces/distribution.py' |
72 | --- lib/lp/registry/interfaces/distribution.py 2009-10-26 18:40:04 +0000 |
73 | +++ lib/lp/registry/interfaces/distribution.py 2009-11-19 04:05:29 +0000 |
74 | @@ -511,13 +511,6 @@ |
75 | bug watches or to products that use_malone. |
76 | """ |
77 | |
78 | - def getCustomLanguageCode(sourcepackagename, language_code): |
79 | - """Look up `ICustomLanguageCode`. |
80 | - |
81 | - A `SourcePackageName` in a Distribution may override some |
82 | - language codes for translation import purposes. |
83 | - """ |
84 | - |
85 | def userCanEdit(user): |
86 | """Can the user edit this distribution?""" |
87 | |
88 | |
89 | === modified file 'lib/lp/registry/interfaces/product.py' |
90 | --- lib/lp/registry/interfaces/product.py 2009-10-26 18:40:04 +0000 |
91 | +++ lib/lp/registry/interfaces/product.py 2009-11-19 04:05:29 +0000 |
92 | @@ -668,13 +668,6 @@ |
93 | def packagedInDistros(): |
94 | """Returns the distributions this product has been packaged in.""" |
95 | |
96 | - def getCustomLanguageCode(language_code): |
97 | - """Look up `ICustomLanguageCode` for `language_code`, if any. |
98 | - |
99 | - Products may override language code definitions for translation |
100 | - import purposes. |
101 | - """ |
102 | - |
103 | def userCanEdit(user): |
104 | """Can the user edit this product?""" |
105 | |
106 | |
107 | === modified file 'lib/lp/registry/model/distribution.py' |
108 | --- lib/lp/registry/model/distribution.py 2009-11-06 21:10:13 +0000 |
109 | +++ lib/lp/registry/model/distribution.py 2009-11-19 04:05:29 +0000 |
110 | @@ -37,7 +37,6 @@ |
111 | BugTargetBase, OfficialBugTagTargetMixin) |
112 | from lp.bugs.model.bugtask import BugTask |
113 | from lp.soyuz.model.build import Build |
114 | -from lp.translations.model.customlanguagecode import CustomLanguageCode |
115 | from lp.registry.model.distributionmirror import DistributionMirror |
116 | from lp.registry.model.distributionsourcepackage import ( |
117 | DistributionSourcePackage) |
118 | @@ -1458,12 +1457,6 @@ |
119 | bugs_affecting_upstream, bugs_with_upstream_bugwatch)) |
120 | return results |
121 | |
122 | - def getCustomLanguageCode(self, sourcepackagename, language_code): |
123 | - """See `IDistribution`.""" |
124 | - return CustomLanguageCode.selectOneBy( |
125 | - distribution=self, sourcepackagename=sourcepackagename, |
126 | - language_code=language_code) |
127 | - |
128 | def setBugSupervisor(self, bug_supervisor, user): |
129 | """See `IHasBugSupervisor`.""" |
130 | self.bug_supervisor = bug_supervisor |
131 | |
132 | === modified file 'lib/lp/registry/model/distributionsourcepackage.py' |
133 | --- lib/lp/registry/model/distributionsourcepackage.py 2009-11-17 21:14:55 +0000 |
134 | +++ lib/lp/registry/model/distributionsourcepackage.py 2009-11-19 04:05:29 +0000 |
135 | @@ -44,11 +44,18 @@ |
136 | DistributionSourcePackageRelease) |
137 | from lp.soyuz.model.publishing import SourcePackagePublishingHistory |
138 | from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease |
139 | +from lp.translations.interfaces.customlanguagecode import ( |
140 | + IHasCustomLanguageCodes) |
141 | +from lp.translations.model.customlanguagecode import ( |
142 | + CustomLanguageCode, HasCustomLanguageCodesMixin) |
143 | + |
144 | |
145 | class DistributionSourcePackage(BugTargetBase, |
146 | SourcePackageQuestionTargetMixin, |
147 | StructuralSubscriptionTargetMixin, |
148 | - HasBranchesMixin, HasMergeProposalsMixin): |
149 | + HasBranchesMixin, |
150 | + HasCustomLanguageCodesMixin, |
151 | + HasMergeProposalsMixin): |
152 | """This is a "Magic Distribution Source Package". It is not an |
153 | SQLObject, but instead it represents a source package with a particular |
154 | name in a particular distribution. You can then ask it all sorts of |
155 | @@ -56,7 +63,8 @@ |
156 | or current release, etc. |
157 | """ |
158 | |
159 | - implements(IDistributionSourcePackage, IQuestionTarget) |
160 | + implements( |
161 | + IDistributionSourcePackage, IHasCustomLanguageCodes, IQuestionTarget) |
162 | |
163 | def __init__(self, distribution, sourcepackagename): |
164 | self.distribution = distribution |
165 | @@ -413,6 +421,19 @@ |
166 | 'BugTask.distribution = %s AND BugTask.sourcepackagename = %s' % |
167 | sqlvalues(self.distribution, self.sourcepackagename)) |
168 | |
169 | + def composeCustomLanguageCodeMatch(self): |
170 | + """See `HasCustomLanguageCodesMixin`.""" |
171 | + return And( |
172 | + CustomLanguageCode.distribution == self.distribution, |
173 | + CustomLanguageCode.sourcepackagename == self.sourcepackagename) |
174 | + |
175 | + def createCustomLanguageCode(self, language_code, language): |
176 | + """See `IHasCustomLanguageCodes`.""" |
177 | + return CustomLanguageCode( |
178 | + distribution=self.distribution, |
179 | + sourcepackagename=self.sourcepackagename, |
180 | + language_code=language_code, language=language) |
181 | + |
182 | @staticmethod |
183 | def getPersonsByEmail(email_addresses): |
184 | """[(EmailAddress,Person), ..] iterable for given email addresses.""" |
185 | |
186 | === modified file 'lib/lp/registry/model/product.py' |
187 | --- lib/lp/registry/model/product.py 2009-11-06 21:06:38 +0000 |
188 | +++ lib/lp/registry/model/product.py 2009-11-19 04:05:29 +0000 |
189 | @@ -1,6 +1,5 @@ |
190 | # Copyright 2009 Canonical Ltd. This software is licensed under the |
191 | # GNU Affero General Public License version 3 (see the file LICENSE). |
192 | - |
193 | # pylint: disable-msg=E0611,W0212 |
194 | |
195 | """Database classes including and related to Product.""" |
196 | @@ -45,7 +44,8 @@ |
197 | from lp.bugs.model.bugwatch import BugWatch |
198 | from lp.registry.model.commercialsubscription import ( |
199 | CommercialSubscription) |
200 | -from lp.translations.model.customlanguagecode import CustomLanguageCode |
201 | +from lp.translations.model.customlanguagecode import ( |
202 | + CustomLanguageCode, HasCustomLanguageCodesMixin) |
203 | from lp.translations.model.potemplate import POTemplate |
204 | from lp.registry.model.distroseries import DistroSeries |
205 | from lp.registry.model.distribution import Distribution |
206 | @@ -81,6 +81,8 @@ |
207 | from canonical.launchpad.interfaces.launchpad import ( |
208 | IHasIcon, IHasLogo, IHasMugshot, ILaunchpadCelebrities, ILaunchpadUsage, |
209 | NotFoundError) |
210 | +from lp.translations.interfaces.customlanguagecode import ( |
211 | + IHasCustomLanguageCodes) |
212 | from canonical.launchpad.interfaces.launchpadstatistic import ( |
213 | ILaunchpadStatisticSet) |
214 | from lp.registry.interfaces.person import IPersonSet |
215 | @@ -166,13 +168,13 @@ |
216 | QuestionTargetMixin, HasTranslationImportsMixin, |
217 | HasAliasMixin, StructuralSubscriptionTargetMixin, |
218 | HasMilestonesMixin, OfficialBugTagTargetMixin, HasBranchesMixin, |
219 | - HasMergeProposalsMixin): |
220 | + HasCustomLanguageCodesMixin, HasMergeProposalsMixin): |
221 | |
222 | """A Product.""" |
223 | |
224 | implements( |
225 | - IFAQTarget, IHasBugSupervisor, IHasIcon, IHasLogo, |
226 | - IHasMugshot, ILaunchpadUsage, IProduct, IQuestionTarget) |
227 | + IFAQTarget, IHasBugSupervisor, IHasCustomLanguageCodes, IHasIcon, |
228 | + IHasLogo, IHasMugshot, ILaunchpadUsage, IProduct, IQuestionTarget) |
229 | |
230 | _table = 'Product' |
231 | |
232 | @@ -964,10 +966,14 @@ |
233 | if bug_supervisor is not None: |
234 | subscription = self.addBugSubscription(bug_supervisor, user) |
235 | |
236 | - def getCustomLanguageCode(self, language_code): |
237 | - """See `IProduct`.""" |
238 | - return CustomLanguageCode.selectOneBy( |
239 | - product=self, language_code=language_code) |
240 | + def composeCustomLanguageCodeMatch(self): |
241 | + """See `HasCustomLanguageCodesMixin`.""" |
242 | + return CustomLanguageCode.product == self |
243 | + |
244 | + def createCustomLanguageCode(self, language_code, language): |
245 | + """See `IHasCustomLanguageCodes`.""" |
246 | + return CustomLanguageCode( |
247 | + product=self, language_code=language_code, language=language) |
248 | |
249 | def userCanEdit(self, user): |
250 | """See `IProduct`.""" |
251 | |
252 | === modified file 'lib/lp/translations/browser/configure.zcml' |
253 | --- lib/lp/translations/browser/configure.zcml 2009-10-31 12:03:43 +0000 |
254 | +++ lib/lp/translations/browser/configure.zcml 2009-11-19 04:05:29 +0000 |
255 | @@ -977,5 +977,50 @@ |
256 | name="+language-packs" |
257 | template="../templates/distroseries-language-packs.pt"/> |
258 | |
259 | + |
260 | +<!-- CustomLanguageCode --> |
261 | + |
262 | + <browser:defaultView |
263 | + for="lp.translations.interfaces.customlanguagecode.ICustomLanguageCode" |
264 | + name="+index" |
265 | + layer="canonical.launchpad.layers.TranslationsLayer"/> |
266 | + <browser:url |
267 | + for="lp.translations.interfaces.customlanguagecode.ICustomLanguageCode" |
268 | + path_expression="string:+customcode/${language_code}" |
269 | + attribute_to_parent="translation_target" |
270 | + /> |
271 | + <browser:page |
272 | + name="+index" |
273 | + for="lp.translations.interfaces.customlanguagecode.ICustomLanguageCode" |
274 | + permission="zope.Public" |
275 | + class="lp.translations.browser.customlanguagecode.CustomLanguageCodeView" |
276 | + template="../templates/customlanguagecode-index.pt" |
277 | + layer="canonical.launchpad.layers.TranslationsLayer"/> |
278 | + |
279 | + <browser:page |
280 | + name="+remove" |
281 | + for="lp.translations.interfaces.customlanguagecode.ICustomLanguageCode" |
282 | + permission="launchpad.Admin" |
283 | + class="lp.translations.browser.customlanguagecode.CustomLanguageCodeRemoveView" |
284 | + template="../../app/templates/generic-edit.pt" |
285 | + layer="canonical.launchpad.layers.TranslationsLayer"/> |
286 | + |
287 | +<!-- IHasCustomLanguageCodes --> |
288 | + |
289 | + <browser:page |
290 | + name="+custom-language-codes" |
291 | + for="lp.translations.interfaces.customlanguagecode.IHasCustomLanguageCodes" |
292 | + layer="canonical.launchpad.layers.TranslationsLayer" |
293 | + class="lp.translations.browser.customlanguagecode.CustomLanguageCodesIndexView" |
294 | + template="../templates/customlanguagecodes-index.pt" |
295 | + permission="zope.Public"/> |
296 | + <browser:page |
297 | + name="+add-custom-language-code" |
298 | + for="lp.translations.interfaces.customlanguagecode.IHasCustomLanguageCodes" |
299 | + layer="canonical.launchpad.layers.TranslationsLayer" |
300 | + class="lp.translations.browser.customlanguagecode.CustomLanguageCodeAddView" |
301 | + template="../templates/customlanguagecode-add.pt" |
302 | + permission="launchpad.Admin"/> |
303 | + |
304 | </facet> |
305 | </configure> |
306 | |
307 | === added file 'lib/lp/translations/browser/customlanguagecode.py' |
308 | --- lib/lp/translations/browser/customlanguagecode.py 1970-01-01 00:00:00 +0000 |
309 | +++ lib/lp/translations/browser/customlanguagecode.py 2009-11-19 04:05:29 +0000 |
310 | @@ -0,0 +1,171 @@ |
311 | +# Copyright 2009 Canonical Ltd. This software is licensed under the |
312 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
313 | + |
314 | +__metaclass__ = type |
315 | + |
316 | +__all__ = [ |
317 | + 'CustomLanguageCodeAddView', |
318 | + 'CustomLanguageCodeBreadcrumb', |
319 | + 'CustomLanguageCodesIndexView', |
320 | + 'CustomLanguageCodeRemoveView', |
321 | + 'CustomLanguageCodeView', |
322 | + 'HasCustomLanguageCodesNavigation', |
323 | + 'HasCustomLanguageCodesTraversalMixin', |
324 | + ] |
325 | + |
326 | + |
327 | +import re |
328 | + |
329 | +from canonical.lazr.utils import smartquote |
330 | + |
331 | +from lp.translations.interfaces.customlanguagecode import ( |
332 | + ICustomLanguageCode, IHasCustomLanguageCodes) |
333 | + |
334 | +from canonical.launchpad.webapp import ( |
335 | + action, canonical_url, LaunchpadFormView, LaunchpadView, Navigation, |
336 | + stepthrough) |
337 | +from canonical.launchpad.webapp.breadcrumb import Breadcrumb |
338 | +from canonical.launchpad.webapp.interfaces import NotFoundError |
339 | +from canonical.launchpad.webapp.menu import structured |
340 | + |
341 | + |
342 | +# Regex for allowable custom language codes. |
343 | +CODE_PATTERN = "[a-zA-Z0-9_-]+$" |
344 | + |
345 | + |
346 | +def check_code(custom_code): |
347 | + """Is this custom language code well-formed?""" |
348 | + return re.match(CODE_PATTERN, custom_code) is not None |
349 | + |
350 | + |
351 | +class CustomLanguageCodeBreadcrumb(Breadcrumb): |
352 | + """Breadcrumb for a `CustomLanguageCode`.""" |
353 | + @property |
354 | + def text(self): |
355 | + return smartquote( |
356 | + 'Custom language code "%s"' % self.context.language_code) |
357 | + |
358 | + |
359 | +class CustomLanguageCodesIndexView(LaunchpadView): |
360 | + """Listing of `CustomLanguageCode`s for a given context.""" |
361 | + |
362 | + page_title = "Custom language codes" |
363 | + |
364 | + @property |
365 | + def label(self): |
366 | + return "Custom language codes for %s" % self.context.displayname |
367 | + |
368 | + |
369 | +class CustomLanguageCodeAddView(LaunchpadFormView): |
370 | + """Create a new custom language code.""" |
371 | + schema = ICustomLanguageCode |
372 | + field_names = ['language_code', 'language'] |
373 | + page_title = "Add new code" |
374 | + |
375 | + create = False |
376 | + |
377 | + @property |
378 | + def label(self): |
379 | + return ( |
380 | + "Add a custom language code for %s" % self.context.displayname) |
381 | + |
382 | + def validate(self, data): |
383 | + self.language_code = data.get('language_code') |
384 | + self.language = data.get('language') |
385 | + if self.language_code is not None: |
386 | + self.language_code = self.language_code.strip() |
387 | + |
388 | + if not self.language_code: |
389 | + self.setFieldError('language_code', "No code was entered.") |
390 | + return |
391 | + |
392 | + if not check_code(self.language_code): |
393 | + self.setFieldError('language_code', "Invalid language code.") |
394 | + return |
395 | + |
396 | + existing_code = self.context.getCustomLanguageCode(self.language_code) |
397 | + if existing_code is not None: |
398 | + if existing_code.language != self.language: |
399 | + self.setFieldError( |
400 | + 'language_code', |
401 | + structured( |
402 | + "There already is a custom language code '%s'." % |
403 | + self.language_code)) |
404 | + return |
405 | + else: |
406 | + self.create = True |
407 | + |
408 | + @action('Add', name='add') |
409 | + def add_action(self, action, data): |
410 | + if self.create: |
411 | + self.context.createCustomLanguageCode( |
412 | + self.language_code, self.language) |
413 | + |
414 | + @property |
415 | + def action_url(self): |
416 | + return "%s/+add-custom-language-code" % canonical_url(self.context) |
417 | + |
418 | + @property |
419 | + def next_url(self): |
420 | + """See `LaunchpadFormView`.""" |
421 | + return "%s/+custom-language-codes" % canonical_url(self.context) |
422 | + |
423 | + @property |
424 | + def cancel_url(self): |
425 | + return self.next_url |
426 | + |
427 | + |
428 | +class CustomLanguageCodeView(LaunchpadView): |
429 | + schema = ICustomLanguageCode |
430 | + |
431 | + |
432 | +class CustomLanguageCodeRemoveView(LaunchpadFormView): |
433 | + """View for removing a `CustomLanguageCode`.""" |
434 | + schema = ICustomLanguageCode |
435 | + field_names = [] |
436 | + |
437 | + page_title = "Remove" |
438 | + |
439 | + @property |
440 | + def code(self): |
441 | + """The custom code.""" |
442 | + return self.context.language_code |
443 | + |
444 | + @property |
445 | + def label(self): |
446 | + return "Remove custom language code '%s'" % self.code |
447 | + |
448 | + @action("Remove") |
449 | + def remove(self, action, data): |
450 | + """Remove this `CustomLanguageCode`.""" |
451 | + code = self.code |
452 | + self.context.translation_target.removeCustomLanguageCode(self.context) |
453 | + self.request.response.addInfoNotification( |
454 | + "Removed custom language code '%s'." % code) |
455 | + |
456 | + @property |
457 | + def next_url(self): |
458 | + return "%s/+custom-language-codes" % canonical_url( |
459 | + self.context.translation_target) |
460 | + |
461 | + @property |
462 | + def cancel_url(self): |
463 | + return self.next_url |
464 | + |
465 | + |
466 | +class HasCustomLanguageCodesTraversalMixin: |
467 | + """Navigate from an `IHasCustomLanguageCodes` to a `CustomLanguageCode`. |
468 | + """ |
469 | + @stepthrough('+customcode') |
470 | + def traverseCustomCode(self, name): |
471 | + """Traverse +customcode URLs.""" |
472 | + if not check_code(name): |
473 | + raise NotFoundError("Invalid custom language code.") |
474 | + |
475 | + return self.context.getCustomLanguageCode(name) |
476 | + |
477 | + |
478 | +class HasCustomLanguageCodesNavigation(Navigation, |
479 | + HasCustomLanguageCodesTraversalMixin): |
480 | + """Generic navigation for `IHasCustomLanguageCodes`.""" |
481 | + usedfor = IHasCustomLanguageCodes |
482 | |
483 | === modified file 'lib/lp/translations/configure.zcml' |
484 | --- lib/lp/translations/configure.zcml 2009-09-17 14:45:59 +0000 |
485 | +++ lib/lp/translations/configure.zcml 2009-11-19 04:05:29 +0000 |
486 | @@ -549,6 +549,11 @@ |
487 | interface="lp.translations.interfaces.translationmessage.ITranslationMessageSet"/> |
488 | </securedutility> |
489 | </facet> |
490 | + <adapter |
491 | + provides="canonical.launchpad.webapp.interfaces.IBreadcrumb" |
492 | + for="lp.translations.interfaces.customlanguagecode.ICustomLanguageCode" |
493 | + factory="lp.translations.browser.customlanguagecode.CustomLanguageCodeBreadcrumb" |
494 | + permission="zope.Public"/> |
495 | <class |
496 | class="lp.translations.model.customlanguagecode.CustomLanguageCode"> |
497 | <allow |
498 | |
499 | === modified file 'lib/lp/translations/interfaces/customlanguagecode.py' |
500 | --- lib/lp/translations/interfaces/customlanguagecode.py 2009-07-17 00:26:05 +0000 |
501 | +++ lib/lp/translations/interfaces/customlanguagecode.py 2009-11-19 04:05:29 +0000 |
502 | @@ -1,5 +1,6 @@ |
503 | # Copyright 2009 Canonical Ltd. This software is licensed under the |
504 | # GNU Affero General Public License version 3 (see the file LICENSE). |
505 | +# pylint: disable-msg=E0213 |
506 | |
507 | """Custom language code.""" |
508 | |
509 | @@ -7,22 +8,76 @@ |
510 | |
511 | __all__ = [ |
512 | 'ICustomLanguageCode', |
513 | + 'IHasCustomLanguageCodes', |
514 | ] |
515 | |
516 | -from zope.interface import Interface, Attribute |
517 | -from zope.schema import Int, TextLine |
518 | +from zope.interface import Interface |
519 | +from zope.schema import Bool, Choice, Int, Object, Set, TextLine |
520 | +from zope.schema.interfaces import IObject |
521 | |
522 | from canonical.launchpad import _ |
523 | |
524 | +from lp.registry.interfaces.distribution import IDistribution |
525 | +from lp.registry.interfaces.product import IProduct |
526 | +from lp.registry.interfaces.sourcepackagename import ISourcePackageName |
527 | + |
528 | |
529 | class ICustomLanguageCode(Interface): |
530 | """`CustomLanguageCode` interface.""" |
531 | |
532 | - id = Int(title=_(u"ID"), required=True, readonly=True) |
533 | - product = Attribute(_(u"Product")) |
534 | - distribution = Attribute(_(u"Distribution")) |
535 | - sourcepackagename = Attribute(_(u"Source package name")) |
536 | - language_code = TextLine(title=_(u"Language code"), required=True, |
537 | - description=_("Language code to treat as special.")) |
538 | - language = Attribute(_(u"Language")) |
539 | - |
540 | + id = Int(title=_("ID"), required=True, readonly=True) |
541 | + product = Object( |
542 | + title=_("Product"), required=False, readonly=True, schema=IProduct) |
543 | + distribution = Object( |
544 | + title=_("Distribution"), required=False, readonly=True, |
545 | + schema=IDistribution) |
546 | + sourcepackagename = Object( |
547 | + title=_("Source package name"), required=False, readonly=True, |
548 | + schema=ISourcePackageName) |
549 | + language_code = TextLine(title=_("Language code"), |
550 | + description=_("Language code to treat as special."), |
551 | + required=True, readonly=False) |
552 | + language = Choice( |
553 | + title=_("Language"), required=False, readonly=False, |
554 | + vocabulary='Language', |
555 | + description=_("Language to map this code to. " |
556 | + "Leave empty to drop translations for this code.")) |
557 | + |
558 | + # Reference back to the IHasCustomLanguageCodes. |
559 | + translation_target = Object( |
560 | + title=_("Context this custom language code applies to"), |
561 | + required=True, readonly=True, schema=IObject) |
562 | + |
563 | + |
564 | +class IHasCustomLanguageCodes(Interface): |
565 | + """A context that can have custom language codes attached. |
566 | + |
567 | + Implemented by `Product` and `SourcePackage`. |
568 | + """ |
569 | + custom_language_codes = Set( |
570 | + title=_("Custom language codes"), |
571 | + description=_("Translations for these language codes are re-routed."), |
572 | + value_type=Object(schema=ICustomLanguageCode), |
573 | + required=False, readonly=False) |
574 | + |
575 | + has_custom_language_codes = Bool( |
576 | + title=_("There are custom language codes in this context."), |
577 | + readonly=True, required=True) |
578 | + |
579 | + def getCustomLanguageCode(language_code): |
580 | + """Retrieve `CustomLanguageCode` for `language_code`. |
581 | + |
582 | + :return: a `CustomLanguageCode`, or None. |
583 | + """ |
584 | + |
585 | + def createCustomLanguageCode(language_code, language): |
586 | + """Create `CustomLanguageCode`. |
587 | + |
588 | + :return: the new `CustomLanguageCode` object. |
589 | + """ |
590 | + |
591 | + def removeCustomLanguageCode(language_code): |
592 | + """Remove `CustomLanguageCode`. |
593 | + |
594 | + :param language_code: A `CustomLanguageCode` object. |
595 | + """ |
596 | |
597 | === modified file 'lib/lp/translations/model/customlanguagecode.py' |
598 | --- lib/lp/translations/model/customlanguagecode.py 2009-07-17 00:26:05 +0000 |
599 | +++ lib/lp/translations/model/customlanguagecode.py 2009-11-19 04:05:29 +0000 |
600 | @@ -5,14 +5,23 @@ |
601 | |
602 | __metaclass__ = type |
603 | |
604 | -__all__ = ['CustomLanguageCode'] |
605 | - |
606 | +__all__ = [ |
607 | + 'CustomLanguageCode', |
608 | + 'HasCustomLanguageCodesMixin', |
609 | + ] |
610 | + |
611 | + |
612 | +from zope.interface import implements |
613 | |
614 | from sqlobject import ForeignKey, StringCol |
615 | -from zope.interface import implements |
616 | +from storm.expr import And |
617 | |
618 | from canonical.database.sqlbase import SQLBase |
619 | +from canonical.launchpad.interfaces.lpstorm import IStore |
620 | + |
621 | from lp.translations.interfaces.customlanguagecode import ICustomLanguageCode |
622 | + |
623 | + |
624 | class CustomLanguageCode(SQLBase): |
625 | """See `ICustomLanguageCode`.""" |
626 | |
627 | @@ -32,3 +41,63 @@ |
628 | language = ForeignKey( |
629 | dbName='language', foreignKey='Language', notNull=False, default=None) |
630 | |
631 | + @property |
632 | + def translation_target(self): |
633 | + """See `ICustomLanguageCode`.""" |
634 | + # Avoid circular imports |
635 | + from lp.registry.model.distributionsourcepackage import ( |
636 | + DistributionSourcePackage) |
637 | + if self.product: |
638 | + return self.product |
639 | + else: |
640 | + return DistributionSourcePackage( |
641 | + self.distribution, self.sourcepackagename) |
642 | + |
643 | + |
644 | +class HasCustomLanguageCodesMixin: |
645 | + """Helper class to implement `IHasCustomLanguageCodes`.""" |
646 | + |
647 | + def composeCustomLanguageCodeMatch(self): |
648 | + """Define in child: compose Storm match clause. |
649 | + |
650 | + This should return a condition for use in a Storm query to match |
651 | + `CustomLanguageCode` objects to `self`. |
652 | + """ |
653 | + raise NotImplementedError("composeCustomLanguageCodeMatch") |
654 | + |
655 | + def createCustomLanguageCode(self, language_code, language): |
656 | + """Define in child. See `IHasCustomLanguageCodes`.""" |
657 | + raise NotImplementedError("createCustomLanguageCode") |
658 | + |
659 | + def _queryCustomLanguageCodes(self, language_code=None): |
660 | + """Query `CustomLanguageCodes` belonging to `self`. |
661 | + |
662 | + :param language_code: Optional custom language code to look for. |
663 | + If not given, all codes will match. |
664 | + :return: A Storm result set. |
665 | + """ |
666 | + match = self.composeCustomLanguageCodeMatch() |
667 | + store = IStore(CustomLanguageCode) |
668 | + if language_code is not None: |
669 | + match = And( |
670 | + match, CustomLanguageCode.language_code == language_code) |
671 | + return store.find(CustomLanguageCode, match) |
672 | + |
673 | + @property |
674 | + def has_custom_language_codes(self): |
675 | + """See `IHasCustomLanguageCodes`.""" |
676 | + return self._queryCustomLanguageCodes().any() is not None |
677 | + |
678 | + @property |
679 | + def custom_language_codes(self): |
680 | + """See `IHasCustomLanguageCodes`.""" |
681 | + return self._queryCustomLanguageCodes().order_by('language_code') |
682 | + |
683 | + def getCustomLanguageCode(self, language_code): |
684 | + """See `IHasCustomLanguageCodes`.""" |
685 | + return self._queryCustomLanguageCodes(language_code).one() |
686 | + |
687 | + def removeCustomLanguageCode(self, custom_code): |
688 | + """See `IHasCustomLanguageCodes`.""" |
689 | + language_code = custom_code.language_code |
690 | + return self._queryCustomLanguageCodes(language_code).remove() |
691 | |
692 | === modified file 'lib/lp/translations/model/translationimportqueue.py' |
693 | --- lib/lp/translations/model/translationimportqueue.py 2009-11-18 11:45:59 +0000 |
694 | +++ lib/lp/translations/model/translationimportqueue.py 2009-11-19 04:05:29 +0000 |
695 | @@ -342,11 +342,12 @@ |
696 | def _findCustomLanguageCode(self, language_code): |
697 | """Find applicable custom language code, if any.""" |
698 | if self.distroseries is not None: |
699 | - return self.distroseries.distribution.getCustomLanguageCode( |
700 | - self.sourcepackagename, language_code) |
701 | + target = self.distroseries.distribution.getSourcePackage( |
702 | + self.sourcepackagename) |
703 | else: |
704 | - return self.productseries.product.getCustomLanguageCode( |
705 | - language_code) |
706 | + target = self.productseries.product |
707 | + |
708 | + return target.getCustomLanguageCode(language_code) |
709 | |
710 | def _guessLanguage(self): |
711 | """See ITranslationImportQueueEntry.""" |
712 | |
713 | === added file 'lib/lp/translations/stories/standalone/custom-language-codes.txt' |
714 | --- lib/lp/translations/stories/standalone/custom-language-codes.txt 1970-01-01 00:00:00 +0000 |
715 | +++ lib/lp/translations/stories/standalone/custom-language-codes.txt 2009-11-19 04:05:29 +0000 |
716 | @@ -0,0 +1,276 @@ |
717 | +Custom Language Codes |
718 | +--------------------- |
719 | + |
720 | +Some projects insist on using nonstandard language codes, such as es_ES |
721 | +for standard Spanish or pt-BR instead of pt_BR. Custom language codes |
722 | +are a feature that helps deal with this during translation import. A |
723 | +custom language code maps a language code as the project (or package) |
724 | +uses it to a language, regardless of whether the code is for an existing |
725 | +language or not. |
726 | + |
727 | +Custom language codes are attached to either a product or a source |
728 | +package. |
729 | + |
730 | + >>> import re |
731 | + >>> from zope.component import getUtility |
732 | + >>> from zope.security.proxy import removeSecurityProxy |
733 | + >>> from canonical.launchpad.interfaces.launchpad import ( |
734 | + ... ILaunchpadCelebrities) |
735 | + |
736 | + >>> def find_custom_language_codes_link(browser): |
737 | + ... """Find reference to custom language codes on a page.""" |
738 | + ... return find_tag_by_id(browser.contents, 'custom-language-codes') |
739 | + |
740 | + >>> login(ANONYMOUS) |
741 | + >>> owner = factory.makePerson(email='o@example.com', password='test') |
742 | + >>> rosetta_admin = factory.makePerson( |
743 | + ... email='r@example.com', password='test') |
744 | + >>> rosetta_admin.join(getUtility(ILaunchpadCelebrities).rosetta_experts) |
745 | + >>> product = factory.makeProduct(displayname="Foo", owner=owner) |
746 | + >>> trunk = product.getSeries('trunk') |
747 | + >>> removeSecurityProxy(product).official_rosetta = True |
748 | + >>> template = factory.makePOTemplate(productseries=trunk) |
749 | + >>> product_page = canonical_url(product, rootsite='translations') |
750 | + >>> logout() |
751 | + |
752 | + >>> owner_browser = setupBrowser("Basic o@example.com:test") |
753 | + |
754 | +An administrator sees the link to the custom language codes on a |
755 | +project's main translations page. |
756 | + |
757 | + >>> admin_browser.open(product_page) |
758 | + >>> tag = find_custom_language_codes_link(admin_browser) |
759 | + >>> print extract_text(tag.renderContents()) |
760 | + If necessary, you may |
761 | + define custom language codes |
762 | + for this project. |
763 | + |
764 | +The link goes to the custom language codes management page. |
765 | + |
766 | + >>> admin_browser.getLink("define custom language codes").click() |
767 | + >>> custom_language_codes_page = admin_browser.url |
768 | + |
769 | +Non-admins, even the project's owner, don't see this link. We do not |
770 | +advertise this feature, since the proper solution is generally to use |
771 | +the right language codes. |
772 | + |
773 | + >>> owner_browser.open(product_page) |
774 | + >>> print find_custom_language_codes_link(owner_browser) |
775 | + None |
776 | + |
777 | +Initially the page shows no custom language codes for the project. |
778 | + |
779 | + >>> tag = find_tag_by_id(admin_browser.contents, 'empty') |
780 | + >>> print extract_text(tag.renderContents()) |
781 | + No custom language codes have been defined. |
782 | + |
783 | +The admin can add a custom language code. |
784 | + |
785 | + >>> admin_browser.getLink("Add a custom language code").click() |
786 | + >>> add_page = admin_browser.url |
787 | + |
788 | + >>> admin_browser.getControl("Language code:").value = 'no' |
789 | + >>> admin_browser.getControl("Language:").value = ['nn'] |
790 | + >>> admin_browser.getControl("Add").click() |
791 | + |
792 | +This leads back to the custom language codes overview, where the new |
793 | +code is now shown. |
794 | + |
795 | + >>> admin_browser.url == custom_language_codes_page |
796 | + True |
797 | + |
798 | + >>> tag = find_tag_by_id(admin_browser.contents, 'nonempty') |
799 | + >>> print extract_text(tag.renderContents()) |
800 | + Foo uses the following custom language codes: |
801 | + Code... ...maps to language |
802 | + no Norwegian Nynorsk |
803 | + |
804 | +There is an overview page for the custom code, though there's not much |
805 | +to see there. |
806 | + |
807 | + >>> admin_browser.getLink("no").click() |
808 | + >>> main = find_main_content(admin_browser.contents) |
809 | + >>> print extract_text(main.renderContents()) |
810 | + Foo Translations Custom language code ...no... |
811 | + For Foo, uploads with the language code |
812 | + “no” |
813 | + are associated with the language |
814 | + Norwegian Nynorsk. |
815 | + remove custom language code |
816 | + custom language codes overview |
817 | + |
818 | +The overview page leads back to the custom language codes overview. |
819 | + |
820 | + >>> code_page = admin_browser.url |
821 | + >>> admin_browser.getLink("custom language codes overview").click() |
822 | + >>> admin_browser.url == custom_language_codes_page |
823 | + True |
824 | + |
825 | + >>> admin_browser.open(code_page) |
826 | + |
827 | +There is also a link for removing codes. The admin follows the link and |
828 | +removes the "no" custom language code. |
829 | + |
830 | + >>> admin_browser.getLink("remove custom language code").click() |
831 | + >>> remove_page = admin_browser.url |
832 | + >>> admin_browser.getControl("Remove").click() |
833 | + |
834 | +This leads back to the overview page. |
835 | + |
836 | + >>> admin_browser.url == custom_language_codes_page |
837 | + True |
838 | + |
839 | + >>> tag = find_tag_by_id(admin_browser.contents, 'empty') |
840 | + >>> print extract_text(tag.renderContents()) |
841 | + No custom language codes have been defined. |
842 | + |
843 | + |
844 | +Non-admin access |
845 | +================ |
846 | + |
847 | +A non-admin can see the page, actually, if they know the URL. This can |
848 | +be convenient for debugging. |
849 | + |
850 | + >>> owner_browser.open(custom_language_codes_page) |
851 | + |
852 | + >>> tag = find_tag_by_id(owner_browser.contents, 'empty') |
853 | + >>> print extract_text(tag.renderContents()) |
854 | + No custom language codes have been defined. |
855 | + |
856 | +However all they get is a read-only version of the page. |
857 | + |
858 | + >>> owner_browser.getLink("Add a custom language code").click() |
859 | + Traceback (most recent call last): |
860 | + ... |
861 | + LinkNotFoundError |
862 | + |
863 | +The page for adding custom language codes is not accessible to them. |
864 | + |
865 | + >>> owner_browser.open(add_page) |
866 | + Traceback (most recent call last): |
867 | + ... |
868 | + Unauthorized: ... |
869 | + |
870 | +And naturally, if an admin creates a custom language code again, a |
871 | +non-admin can't remove it. |
872 | + |
873 | + >>> admin_browser.open(add_page) |
874 | + >>> admin_browser.getControl("Language code:").value = 'no' |
875 | + >>> admin_browser.getControl("Language:").value = ['nn'] |
876 | + >>> admin_browser.getControl("Add").click() |
877 | + |
878 | + >>> owner_browser.open(custom_language_codes_page) |
879 | + >>> tag = find_tag_by_id(owner_browser.contents, 'nonempty') |
880 | + >>> print extract_text(tag.renderContents()) |
881 | + Foo uses the following custom language codes: |
882 | + Code... ...maps to language |
883 | + no Norwegian Nynorsk |
884 | + |
885 | + >>> owner_browser.getLink("no").click() |
886 | + >>> owner_browser.getLink("remove custom language code") |
887 | + Traceback (most recent call last): |
888 | + ... |
889 | + LinkNotFoundError |
890 | + |
891 | + >>> owner_browser.open(remove_page) |
892 | + Traceback (most recent call last): |
893 | + ... |
894 | + Unauthorized: ... |
895 | + |
896 | + |
897 | +Source packages |
898 | +=============== |
899 | + |
900 | +The story for source packages is very similar to that for products. In |
901 | +this case, the custom language code is tied to the distribution source |
902 | +package--i.e. the combination of a distribution and a source package |
903 | +name. However, since there is no Translations page for that type of |
904 | +object (and we'd probably never go there if there were), the link is |
905 | +shown on the source package page. |
906 | + |
907 | + >>> login(ANONYMOUS) |
908 | + >>> from lp.registry.model.sourcepackage import SourcePackage |
909 | + >>> from lp.registry.model.sourcepackagename import SourcePackageName |
910 | + |
911 | + >>> distro = factory.makeDistribution('distro') |
912 | + >>> distroseries = factory.makeDistroRelease(distribution=distro) |
913 | + >>> sourcepackagename = SourcePackageName(name='bar') |
914 | + >>> package = factory.makeSourcePackage( |
915 | + ... sourcepackagename=sourcepackagename, distroseries=distroseries) |
916 | + >>> removeSecurityProxy(distro).official_rosetta = True |
917 | + >>> other_series = factory.makeDistroRelease(distribution=distro) |
918 | + >>> template = factory.makePOTemplate( |
919 | + ... distroseries=package.distroseries, |
920 | + ... sourcepackagename=package.sourcepackagename) |
921 | + >>> package_page = canonical_url(package, rootsite="translations") |
922 | + >>> page_in_other_series = canonical_url(SourcePackage( |
923 | + ... distroseries=other_series, |
924 | + ... sourcepackagename=package.sourcepackagename), |
925 | + ... rootsite="translations") |
926 | + >>> logout() |
927 | + |
928 | + >>> admin_browser.open(package_page) |
929 | + |
930 | +Of course in this case, the notice about there being no custom language |
931 | +codes talks about a package, not a project. |
932 | + |
933 | + >>> tag = find_custom_language_codes_link(admin_browser) |
934 | + >>> print extract_text(tag.renderContents()) |
935 | + If necessary, you may |
936 | + define custom language codes |
937 | + for this package. |
938 | + |
939 | + >>> admin_browser.getLink("define custom language codes").click() |
940 | + >>> custom_language_codes_page = admin_browser.url |
941 | + |
942 | + >>> tag = find_tag_by_id(admin_browser.contents, 'empty') |
943 | + >>> print extract_text(tag.renderContents()) |
944 | + No custom language codes have been defined. |
945 | + |
946 | +Again, an admin can add a language code. |
947 | + |
948 | + >>> admin_browser.getLink("Add a custom language code").click() |
949 | + >>> add_page = admin_browser.url |
950 | + |
951 | + >>> admin_browser.getControl("Language code:").value = 'pt-br' |
952 | + >>> admin_browser.getControl("Language:").value = ['pt_BR'] |
953 | + >>> admin_browser.getControl("Add").click() |
954 | + |
955 | +The language code is displayed. |
956 | + |
957 | + >>> tag = find_tag_by_id(admin_browser.contents, 'nonempty') |
958 | + >>> print extract_text(tag.renderContents()) |
959 | + bar in distro uses the following custom language codes: |
960 | + Code... ...maps to language |
961 | + pt-br Portuguese (Brazil) |
962 | + |
963 | +It's also displayed identically on the same package but in another |
964 | +release series of the same distribution. |
965 | + |
966 | + >>> admin_browser.open(page_in_other_series) |
967 | + >>> tag = find_custom_language_codes_link(admin_browser) |
968 | + >>> print extract_text(tag.renderContents()) |
969 | + If necessary, you may |
970 | + define custom language codes |
971 | + for this package. |
972 | + |
973 | + >>> admin_browser.getLink("define custom language codes").click() |
974 | + >>> tag = find_tag_by_id(admin_browser.contents, 'nonempty') |
975 | + >>> print extract_text(tag.renderContents()) |
976 | + bar in distro uses the following custom language codes: |
977 | + Code... ...maps to language |
978 | + pt-br Portuguese (Brazil) |
979 | + |
980 | + |
981 | +The new code has a link there... |
982 | + |
983 | + >>> admin_browser.getLink("pt-br").click() |
984 | + |
985 | +...and can be deleted. |
986 | + |
987 | + >>> admin_browser.getLink("remove custom language code").click() |
988 | + >>> admin_browser.getControl("Remove").click() |
989 | + |
990 | + >>> tag = find_tag_by_id(admin_browser.contents, 'empty') |
991 | + >>> print extract_text(tag.renderContents()) |
992 | + No custom language codes have been defined. |
993 | |
994 | === added file 'lib/lp/translations/templates/customlanguagecode-add.pt' |
995 | --- lib/lp/translations/templates/customlanguagecode-add.pt 1970-01-01 00:00:00 +0000 |
996 | +++ lib/lp/translations/templates/customlanguagecode-add.pt 2009-11-19 04:05:29 +0000 |
997 | @@ -0,0 +1,29 @@ |
998 | +<html |
999 | + xmlns="http://www.w3.org/1999/xhtml" |
1000 | + xmlns:tal="http://xml.zope.org/namespaces/tal" |
1001 | + xmlns:metal="http://xml.zope.org/namespaces/metal" |
1002 | + xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
1003 | + metal:use-macro="view/macro:page/main_only" |
1004 | + i18n:domain="launchpad"> |
1005 | + |
1006 | +<body> |
1007 | + <div metal:fill-slot="main"> |
1008 | + <div metal:use-macro="context/@@launchpad_form/form"> |
1009 | + <metal:extra-info metal:fill-slot="extra_info"> |
1010 | + <p> |
1011 | + Enter a language code, and select what language it should map |
1012 | + to during upload auto-approval. |
1013 | + </p> |
1014 | + <p> |
1015 | + Avoid using this capability if possible, since it makes |
1016 | + it harder to keep track of what goes where. For cases with |
1017 | + few templates, where a code would only cover one or two |
1018 | + translation files, it may be better to approve those |
1019 | + uploads manually. Launchpad will remember the filename and |
1020 | + approve it automatically next time it comes along. |
1021 | + </p> |
1022 | + </metal:extra-info> |
1023 | + </div> |
1024 | + </div> |
1025 | +</body> |
1026 | +</html> |
1027 | |
1028 | === added file 'lib/lp/translations/templates/customlanguagecode-index.pt' |
1029 | --- lib/lp/translations/templates/customlanguagecode-index.pt 1970-01-01 00:00:00 +0000 |
1030 | +++ lib/lp/translations/templates/customlanguagecode-index.pt 2009-11-19 04:05:29 +0000 |
1031 | @@ -0,0 +1,46 @@ |
1032 | +<html |
1033 | + xmlns="http://www.w3.org/1999/xhtml" |
1034 | + xmlns:tal="http://xml.zope.org/namespaces/tal" |
1035 | + xmlns:metal="http://xml.zope.org/namespaces/metal" |
1036 | + xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
1037 | + metal:use-macro="view/macro:page/main_only" |
1038 | + i18n:domain="launchpad" |
1039 | + > |
1040 | +<body> |
1041 | + <div metal:fill-slot="main"> |
1042 | + <div class="top-portlet"> |
1043 | + For |
1044 | + <tal:target replace="context/translation_target/displayname"> |
1045 | + Evolution</tal:target>, |
1046 | + uploads with the language code |
1047 | + “<strong tal:content="context/language_code">pt-BR</strong>” |
1048 | + <tal:language condition="context/language"> |
1049 | + are associated with the language |
1050 | + <a tal:replace="structure context/language/fmt:link"> |
1051 | + Brazilian Portuguese</a>. |
1052 | + </tal:language> |
1053 | + <tal:no-language condition="not: context/language"> |
1054 | + are ignored. |
1055 | + </tal:no-language> |
1056 | + </div> |
1057 | + |
1058 | + <div class="portlet"> |
1059 | + <ul class="horizontal"> |
1060 | + <li tal:condition="context/required:launchpad.Admin"> |
1061 | + <a class="remove sprite" |
1062 | + tal:attributes="href context/fmt:url/+remove"> |
1063 | + remove custom language code |
1064 | + </a> |
1065 | + </li> |
1066 | + <li> |
1067 | + <a class="info sprite" |
1068 | + tal:attributes="href context/translation_target/fmt:url/+custom-language-codes"> |
1069 | + custom language codes overview |
1070 | + </a> |
1071 | + </li> |
1072 | + </ul> |
1073 | + </div> |
1074 | + </div> |
1075 | +</body> |
1076 | +</html> |
1077 | + |
1078 | |
1079 | === added file 'lib/lp/translations/templates/customlanguagecodes-index.pt' |
1080 | --- lib/lp/translations/templates/customlanguagecodes-index.pt 1970-01-01 00:00:00 +0000 |
1081 | +++ lib/lp/translations/templates/customlanguagecodes-index.pt 2009-11-19 04:05:29 +0000 |
1082 | @@ -0,0 +1,80 @@ |
1083 | +<html |
1084 | + xmlns="http://www.w3.org/1999/xhtml" |
1085 | + xmlns:tal="http://xml.zope.org/namespaces/tal" |
1086 | + xmlns:metal="http://xml.zope.org/namespaces/metal" |
1087 | + xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
1088 | + metal:use-macro="view/macro:page/main_only" |
1089 | + i18n:domain="launchpad" |
1090 | + > |
1091 | +<body> |
1092 | + <div metal:fill-slot="main"> |
1093 | + <div class="top-portlet"> |
1094 | + <p> |
1095 | + You can define custom language codes for |
1096 | + <tal:target replace="structure context/displayname">Evolution</tal:target> |
1097 | + here. Custom language codes will be treated like proper |
1098 | + language codes by translations imports, except each is |
1099 | + associated with a language you choose. |
1100 | + </p> |
1101 | + <p> |
1102 | + Avoid doing this if possible; it makes it harder to keep track |
1103 | + of what goes where during translations import, and why. |
1104 | + </p> |
1105 | + </div> |
1106 | + <div tal:condition="context/has_custom_language_codes" |
1107 | + class="portlet" |
1108 | + id="nonempty"> |
1109 | + <p> |
1110 | + <tal:block replace="context/displayname">Evolution</tal:block> |
1111 | + uses the following custom language codes: |
1112 | + </p> |
1113 | + <table class="listing" style="max-width:800px"> |
1114 | + <thead> |
1115 | + <tr> |
1116 | + <th>Code...</th> |
1117 | + <th>...maps to language</th> |
1118 | + <th tal:condition="context/required:launchpad.Admin"></th> |
1119 | + </tr> |
1120 | + </thead> |
1121 | + <tbody> |
1122 | + <tr tal:repeat="entry context/custom_language_codes"> |
1123 | + <td align="center"> |
1124 | + <a tal:attributes="href entry/fmt:url" |
1125 | + tal:content="entry/language_code">pt-PT</a> |
1126 | + </td> |
1127 | + <td> |
1128 | + <a tal:condition="entry/language" |
1129 | + tal:replace="structure entry/language/fmt:link"> |
1130 | + Portuguese (pt) |
1131 | + </a> |
1132 | + <tal:nolanguage condition="not: entry/language"> |
1133 | + — |
1134 | + </tal:nolanguage> |
1135 | + </td> |
1136 | + <td tal:condition="context/required:launchpad.Admin"> |
1137 | + <a tal:attributes="href entry/fmt:url/+remove" |
1138 | + alt="Remove" |
1139 | + title="Remove" |
1140 | + class="remove sprite"></a> |
1141 | + </td> |
1142 | + </tr> |
1143 | + </tbody> |
1144 | + </table> |
1145 | + </div> |
1146 | + |
1147 | + <p tal:condition="not: context/has_custom_language_codes" |
1148 | + class="portlet" |
1149 | + id="empty"> |
1150 | + No custom language codes have been defined. |
1151 | + </p> |
1152 | + |
1153 | + <div> |
1154 | + <a tal:attributes="href context/fmt:url/+add-custom-language-code" |
1155 | + tal:condition="context/required:launchpad.Admin" |
1156 | + class="add sprite"> |
1157 | + Add a custom language code |
1158 | + </a> |
1159 | + </div> |
1160 | + </div> |
1161 | +</body> |
1162 | +</html> |
1163 | |
1164 | === modified file 'lib/lp/translations/templates/product-portlet-translatables.pt' |
1165 | --- lib/lp/translations/templates/product-portlet-translatables.pt 2009-10-31 12:03:43 +0000 |
1166 | +++ lib/lp/translations/templates/product-portlet-translatables.pt 2009-11-19 04:05:29 +0000 |
1167 | @@ -63,4 +63,16 @@ |
1168 | </div> |
1169 | |
1170 | </div> |
1171 | + |
1172 | +<div class="portlet" |
1173 | + tal:condition="context/required:launchpad.Admin" |
1174 | + id="custom-language-codes"> |
1175 | + If necessary, you may |
1176 | + <a tal:attributes="href context/fmt:url/+custom-language-codes" |
1177 | + class="edit sprite"> |
1178 | + define custom language codes |
1179 | + </a> |
1180 | + for this project. |
1181 | +</div> |
1182 | + |
1183 | </tal:root> |
1184 | |
1185 | === modified file 'lib/lp/translations/templates/sourcepackage-translations.pt' |
1186 | --- lib/lp/translations/templates/sourcepackage-translations.pt 2009-09-25 16:07:06 +0000 |
1187 | +++ lib/lp/translations/templates/sourcepackage-translations.pt 2009-11-19 04:05:29 +0000 |
1188 | @@ -39,6 +39,15 @@ |
1189 | <a tal:attributes="href context/menu:navigation/download/url"> |
1190 | download a full tarball</a> with translations. |
1191 | </p> |
1192 | + <p tal:condition="context/required:launchpad.Admin" |
1193 | + id="custom-language-codes"> |
1194 | + If necessary, you may |
1195 | + <a tal:attributes="href context/distribution_sourcepackage/fmt:url/+custom-language-codes" |
1196 | + class="edit sprite"> |
1197 | + define custom language codes |
1198 | + </a> |
1199 | + for this package. |
1200 | + </p> |
1201 | </div> |
1202 | </div> |
1203 | </div> |
1204 | |
1205 | === modified file 'lib/lp/translations/tests/test_autoapproval.py' |
1206 | --- lib/lp/translations/tests/test_autoapproval.py 2009-11-17 09:50:33 +0000 |
1207 | +++ lib/lp/translations/tests/test_autoapproval.py 2009-11-19 04:05:29 +0000 |
1208 | @@ -89,26 +89,23 @@ |
1209 | self.assertEqual(fresh_product.getCustomLanguageCode('pt_PT'), None) |
1210 | |
1211 | fresh_distro = Distribution.byName('gentoo') |
1212 | - nocode = fresh_distro.getCustomLanguageCode( |
1213 | - self.sourcepackagename, 'nocode') |
1214 | + gentoo_package = fresh_distro.getSourcePackage(self.sourcepackagename) |
1215 | + nocode = gentoo_package.getCustomLanguageCode('nocode') |
1216 | self.assertEqual(nocode, None) |
1217 | - brazilian = fresh_distro.getCustomLanguageCode( |
1218 | - self.sourcepackagename, 'Brazilian') |
1219 | + brazilian = gentoo_package.getCustomLanguageCode('Brazilian') |
1220 | self.assertEqual(brazilian, None) |
1221 | |
1222 | - fresh_package = SourcePackageName.byName('cnews') |
1223 | - self.assertEqual(self.distro.getCustomLanguageCode( |
1224 | - fresh_package, 'nocode'), None) |
1225 | - self.assertEqual(self.distro.getCustomLanguageCode( |
1226 | - fresh_package, 'Brazilian'), None) |
1227 | + cnews = SourcePackageName.byName('cnews') |
1228 | + cnews_package = self.distro.getSourcePackage(cnews) |
1229 | + self.assertEqual(cnews_package.getCustomLanguageCode('nocode'), None) |
1230 | + self.assertEqual( |
1231 | + cnews_package.getCustomLanguageCode('Brazilian'), None) |
1232 | |
1233 | def test_UnsuccessfulCustomLanguageCodeLookup(self): |
1234 | # Look up nonexistent custom language code for product. |
1235 | self.assertEqual(self.product.getCustomLanguageCode('nocode'), None) |
1236 | - self.assertEqual( |
1237 | - self.distro.getCustomLanguageCode( |
1238 | - self.sourcepackagename, 'nocode'), |
1239 | - None) |
1240 | + package = self.distro.getSourcePackage(self.sourcepackagename) |
1241 | + self.assertEqual(package.getCustomLanguageCode('nocode'), None) |
1242 | |
1243 | def test_SuccessfulProductCustomLanguageCodeLookup(self): |
1244 | # Look up custom language code. |
1245 | @@ -122,8 +119,8 @@ |
1246 | |
1247 | def test_SuccessfulPackageCustomLanguageCodeLookup(self): |
1248 | # Look up custom language code. |
1249 | - Brazilian_code = self.distro.getCustomLanguageCode( |
1250 | - self.sourcepackagename, 'Brazilian') |
1251 | + package = self.distro.getSourcePackage(self.sourcepackagename) |
1252 | + Brazilian_code = package.getCustomLanguageCode('Brazilian') |
1253 | self.assertEqual(Brazilian_code, self.package_codes['Brazilian']) |
1254 | self.assertEqual(Brazilian_code.product, None) |
1255 | self.assertEqual(Brazilian_code.distribution, self.distro) |
= Bug 271747: Custom Language Codes UI =
This is an oversized branch, and it's not particularly urgent. Review it only if you feel like it, or something's changed and we've begged you to do it.
Some projects or Ubuntu packages insist on using nonstandard language codes. To deal with those we created, long ago, "custom language codes" which allow the import approval process to recognize that an upload with a given "weird" language code is actually, by the project's or package's standard, a translation for a given language.
We don't advertise this feature, and it's not frequently used. Project owners do not get access to it. It's really only there for a few problem cases like OpenOffice.org, which not only use nonstandard language codes but also add or move templates regularly. If it were just a matter of a single translation, you could simply approve it once and the approver would remember the path and associate it with the right translation.
However, it is a pain in the neck to have to write SQL, get approval, and go through LOSAs every time we want one of these babies added. So this branch finally creates a simple UI for dealing with them.
The simple UI lists custom language codes, shows them in detail, adds them, and removes them. For non-admins there's a read-only view of the whole thing, which may come in handy when debugging, but we don't link to them because generally speaking, custom language codes are probably the wrong solution for a project having problem with its language codes. And it adds one more knob to twiddle, providing one more thing that may go wrong with import approvals.
One limitation: this branch allows only Launchpad admins to manipulate custom language codes. In future we'll also want to allow Rosetta admins to do this. But that required some fiddling to check for the right permissions. This branch is already oversized, so instead I intend to file a separate bug about it.
== Tests == language- codes.txt
{{{
./bin/test -vv -t custom-
}}}
== Demo, Q/A, and UI review ==
Log in as a Launchpad administrator. Go to the Translations page for a project or source package with translations.
On staging: /translations. staging. launchpad. net/josm/
https:/
On a development machine: /translations. launchpad. dev/evolution/
https:/
At the bottom of the right-hand side column you'll see a note: "If necessary, you may <define custom language codes> for this project." Click on the link to get to the custom language codes UI for the project.
The page it takes you to is an overview, probably of nothing as yet. Try adding a custom language code: map a custom "language code" to an actual language, or to no language at all. (If you map to no language, uploads with that language code string will quietly disappear—useful but one of those things that can cause support headaches if misused). "Weird" characters are rejected; we don't want to be able to redefine the ".po" filename extension to refer to a language or anything like that.
Whatever you add will show up in the listing. You can also remove or view the codes. Viewing them doesn't give you much, besides a link to the language, but it was needed to make t...