Merge lp:~jtv/launchpad/custom-language-codes into lp:launchpad

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
Reviewer Review Type Date Requested Status
Abel Deuring (community) code Approve
Martin Albisetti ui Pending
Review via email: mp+14555@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :
Download full text (3.3 KiB)

= 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 ==
{{{
./bin/test -vv -t custom-language-codes.txt
}}}

== 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:
    https://translations.staging.launchpad.net/josm/

On a development machine:
    https://translations.launchpad.dev/evolution/

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...

Read more...

Revision history for this message
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 HasCustomlagnuageCodeTraversalMixin, and a no longer needed definition of getCustomLangaugeCode in IDistribution), pleased fix them.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/registry/browser/distributionsourcepackage.py'
--- lib/lp/registry/browser/distributionsourcepackage.py 2009-11-10 21:33:20 +0000
+++ lib/lp/registry/browser/distributionsourcepackage.py 2009-11-19 04:05:29 +0000
@@ -50,6 +50,8 @@
50from lp.registry.browser.packaging import PackagingDeleteView50from lp.registry.browser.packaging import PackagingDeleteView
51from lp.registry.interfaces.pocket import pocketsuffix51from lp.registry.interfaces.pocket import pocketsuffix
52from lp.registry.interfaces.product import IDistributionSourcePackage52from lp.registry.interfaces.product import IDistributionSourcePackage
53from lp.translations.browser.customlanguagecode import (
54 HasCustomLanguageCodesTraversalMixin)
5355
5456
55class DistributionSourcePackageBreadcrumb(Breadcrumb):57class DistributionSourcePackageBreadcrumb(Breadcrumb):
@@ -117,7 +119,8 @@
117119
118120
119class DistributionSourcePackageNavigation(Navigation,121class DistributionSourcePackageNavigation(Navigation,
120 BugTargetTraversalMixin, QuestionTargetTraversalMixin,122 BugTargetTraversalMixin, HasCustomLanguageCodesTraversalMixin,
123 QuestionTargetTraversalMixin,
121 StructuralSubscriptionTargetTraversalMixin):124 StructuralSubscriptionTargetTraversalMixin):
122125
123 usedfor = IDistributionSourcePackage126 usedfor = IDistributionSourcePackage
124127
=== modified file 'lib/lp/registry/browser/product.py'
--- lib/lp/registry/browser/product.py 2009-11-11 16:57:29 +0000
+++ lib/lp/registry/browser/product.py 2009-11-19 04:05:29 +0000
@@ -86,6 +86,8 @@
86from lp.answers.browser.faqtarget import FAQTargetNavigationMixin86from lp.answers.browser.faqtarget import FAQTargetNavigationMixin
87from canonical.launchpad.browser.feeds import FeedsMixin87from canonical.launchpad.browser.feeds import FeedsMixin
88from lp.registry.browser.productseries import get_series_branch_error88from lp.registry.browser.productseries import get_series_branch_error
89from lp.translations.browser.customlanguagecode import (
90 HasCustomLanguageCodesTraversalMixin)
89from canonical.launchpad.browser.multistep import MultiStepView, StepView91from canonical.launchpad.browser.multistep import MultiStepView, StepView
90from lp.answers.browser.questiontarget import (92from lp.answers.browser.questiontarget import (
91 QuestionTargetFacetMixin, QuestionTargetTraversalMixin)93 QuestionTargetFacetMixin, QuestionTargetTraversalMixin)
@@ -118,8 +120,8 @@
118120
119class ProductNavigation(121class ProductNavigation(
120 Navigation, BugTargetTraversalMixin,122 Navigation, BugTargetTraversalMixin,
121 FAQTargetNavigationMixin, QuestionTargetTraversalMixin,123 FAQTargetNavigationMixin, HasCustomLanguageCodesTraversalMixin,
122 StructuralSubscriptionTargetTraversalMixin):124 QuestionTargetTraversalMixin, StructuralSubscriptionTargetTraversalMixin):
123125
124 usedfor = IProduct126 usedfor = IProduct
125127
126128
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml 2009-11-15 01:05:49 +0000
+++ lib/lp/registry/configure.zcml 2009-11-19 04:05:29 +0000
@@ -353,6 +353,9 @@
353 <class353 <class
354 class="lp.registry.model.distributionsourcepackage.DistributionSourcePackage">354 class="lp.registry.model.distributionsourcepackage.DistributionSourcePackage">
355 <allow355 <allow
356 interface="lp.translations.interfaces.customlanguagecode.IHasCustomLanguageCodes"/>
357
358 <allow
356 attributes="359 attributes="
357 distribution360 distribution
358 development_version361 development_version
@@ -1045,6 +1048,8 @@
1045 interface="lp.registry.interfaces.product.IProductPublic"/>1048 interface="lp.registry.interfaces.product.IProductPublic"/>
1046 <allow1049 <allow
1047 interface="lp.translations.interfaces.translationimportqueue.IHasTranslationImports"/>1050 interface="lp.translations.interfaces.translationimportqueue.IHasTranslationImports"/>
1051 <allow
1052 interface="lp.translations.interfaces.customlanguagecode.IHasCustomLanguageCodes"/>
1048 <require1053 <require
1049 permission="launchpad.Driver"1054 permission="launchpad.Driver"
1050 interface="lp.registry.interfaces.product.IProductDriverRestricted"/>1055 interface="lp.registry.interfaces.product.IProductDriverRestricted"/>
10511056
=== modified file 'lib/lp/registry/interfaces/distribution.py'
--- lib/lp/registry/interfaces/distribution.py 2009-10-26 18:40:04 +0000
+++ lib/lp/registry/interfaces/distribution.py 2009-11-19 04:05:29 +0000
@@ -511,13 +511,6 @@
511 bug watches or to products that use_malone.511 bug watches or to products that use_malone.
512 """512 """
513513
514 def getCustomLanguageCode(sourcepackagename, language_code):
515 """Look up `ICustomLanguageCode`.
516
517 A `SourcePackageName` in a Distribution may override some
518 language codes for translation import purposes.
519 """
520
521 def userCanEdit(user):514 def userCanEdit(user):
522 """Can the user edit this distribution?"""515 """Can the user edit this distribution?"""
523516
524517
=== modified file 'lib/lp/registry/interfaces/product.py'
--- lib/lp/registry/interfaces/product.py 2009-10-26 18:40:04 +0000
+++ lib/lp/registry/interfaces/product.py 2009-11-19 04:05:29 +0000
@@ -668,13 +668,6 @@
668 def packagedInDistros():668 def packagedInDistros():
669 """Returns the distributions this product has been packaged in."""669 """Returns the distributions this product has been packaged in."""
670670
671 def getCustomLanguageCode(language_code):
672 """Look up `ICustomLanguageCode` for `language_code`, if any.
673
674 Products may override language code definitions for translation
675 import purposes.
676 """
677
678 def userCanEdit(user):671 def userCanEdit(user):
679 """Can the user edit this product?"""672 """Can the user edit this product?"""
680673
681674
=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py 2009-11-06 21:10:13 +0000
+++ lib/lp/registry/model/distribution.py 2009-11-19 04:05:29 +0000
@@ -37,7 +37,6 @@
37 BugTargetBase, OfficialBugTagTargetMixin)37 BugTargetBase, OfficialBugTagTargetMixin)
38from lp.bugs.model.bugtask import BugTask38from lp.bugs.model.bugtask import BugTask
39from lp.soyuz.model.build import Build39from lp.soyuz.model.build import Build
40from lp.translations.model.customlanguagecode import CustomLanguageCode
41from lp.registry.model.distributionmirror import DistributionMirror40from lp.registry.model.distributionmirror import DistributionMirror
42from lp.registry.model.distributionsourcepackage import (41from lp.registry.model.distributionsourcepackage import (
43 DistributionSourcePackage)42 DistributionSourcePackage)
@@ -1458,12 +1457,6 @@
1458 bugs_affecting_upstream, bugs_with_upstream_bugwatch))1457 bugs_affecting_upstream, bugs_with_upstream_bugwatch))
1459 return results1458 return results
14601459
1461 def getCustomLanguageCode(self, sourcepackagename, language_code):
1462 """See `IDistribution`."""
1463 return CustomLanguageCode.selectOneBy(
1464 distribution=self, sourcepackagename=sourcepackagename,
1465 language_code=language_code)
1466
1467 def setBugSupervisor(self, bug_supervisor, user):1460 def setBugSupervisor(self, bug_supervisor, user):
1468 """See `IHasBugSupervisor`."""1461 """See `IHasBugSupervisor`."""
1469 self.bug_supervisor = bug_supervisor1462 self.bug_supervisor = bug_supervisor
14701463
=== modified file 'lib/lp/registry/model/distributionsourcepackage.py'
--- lib/lp/registry/model/distributionsourcepackage.py 2009-11-17 21:14:55 +0000
+++ lib/lp/registry/model/distributionsourcepackage.py 2009-11-19 04:05:29 +0000
@@ -44,11 +44,18 @@
44 DistributionSourcePackageRelease)44 DistributionSourcePackageRelease)
45from lp.soyuz.model.publishing import SourcePackagePublishingHistory45from lp.soyuz.model.publishing import SourcePackagePublishingHistory
46from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease46from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
47from lp.translations.interfaces.customlanguagecode import (
48 IHasCustomLanguageCodes)
49from lp.translations.model.customlanguagecode import (
50 CustomLanguageCode, HasCustomLanguageCodesMixin)
51
4752
48class DistributionSourcePackage(BugTargetBase,53class DistributionSourcePackage(BugTargetBase,
49 SourcePackageQuestionTargetMixin,54 SourcePackageQuestionTargetMixin,
50 StructuralSubscriptionTargetMixin,55 StructuralSubscriptionTargetMixin,
51 HasBranchesMixin, HasMergeProposalsMixin):56 HasBranchesMixin,
57 HasCustomLanguageCodesMixin,
58 HasMergeProposalsMixin):
52 """This is a "Magic Distribution Source Package". It is not an59 """This is a "Magic Distribution Source Package". It is not an
53 SQLObject, but instead it represents a source package with a particular60 SQLObject, but instead it represents a source package with a particular
54 name in a particular distribution. You can then ask it all sorts of61 name in a particular distribution. You can then ask it all sorts of
@@ -56,7 +63,8 @@
56 or current release, etc.63 or current release, etc.
57 """64 """
5865
59 implements(IDistributionSourcePackage, IQuestionTarget)66 implements(
67 IDistributionSourcePackage, IHasCustomLanguageCodes, IQuestionTarget)
6068
61 def __init__(self, distribution, sourcepackagename):69 def __init__(self, distribution, sourcepackagename):
62 self.distribution = distribution70 self.distribution = distribution
@@ -413,6 +421,19 @@
413 'BugTask.distribution = %s AND BugTask.sourcepackagename = %s' %421 'BugTask.distribution = %s AND BugTask.sourcepackagename = %s' %
414 sqlvalues(self.distribution, self.sourcepackagename))422 sqlvalues(self.distribution, self.sourcepackagename))
415423
424 def composeCustomLanguageCodeMatch(self):
425 """See `HasCustomLanguageCodesMixin`."""
426 return And(
427 CustomLanguageCode.distribution == self.distribution,
428 CustomLanguageCode.sourcepackagename == self.sourcepackagename)
429
430 def createCustomLanguageCode(self, language_code, language):
431 """See `IHasCustomLanguageCodes`."""
432 return CustomLanguageCode(
433 distribution=self.distribution,
434 sourcepackagename=self.sourcepackagename,
435 language_code=language_code, language=language)
436
416 @staticmethod437 @staticmethod
417 def getPersonsByEmail(email_addresses):438 def getPersonsByEmail(email_addresses):
418 """[(EmailAddress,Person), ..] iterable for given email addresses."""439 """[(EmailAddress,Person), ..] iterable for given email addresses."""
419440
=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py 2009-11-06 21:06:38 +0000
+++ lib/lp/registry/model/product.py 2009-11-19 04:05:29 +0000
@@ -1,6 +1,5 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4# pylint: disable-msg=E0611,W02123# pylint: disable-msg=E0611,W0212
54
6"""Database classes including and related to Product."""5"""Database classes including and related to Product."""
@@ -45,7 +44,8 @@
45from lp.bugs.model.bugwatch import BugWatch44from lp.bugs.model.bugwatch import BugWatch
46from lp.registry.model.commercialsubscription import (45from lp.registry.model.commercialsubscription import (
47 CommercialSubscription)46 CommercialSubscription)
48from lp.translations.model.customlanguagecode import CustomLanguageCode47from lp.translations.model.customlanguagecode import (
48 CustomLanguageCode, HasCustomLanguageCodesMixin)
49from lp.translations.model.potemplate import POTemplate49from lp.translations.model.potemplate import POTemplate
50from lp.registry.model.distroseries import DistroSeries50from lp.registry.model.distroseries import DistroSeries
51from lp.registry.model.distribution import Distribution51from lp.registry.model.distribution import Distribution
@@ -81,6 +81,8 @@
81from canonical.launchpad.interfaces.launchpad import (81from canonical.launchpad.interfaces.launchpad import (
82 IHasIcon, IHasLogo, IHasMugshot, ILaunchpadCelebrities, ILaunchpadUsage,82 IHasIcon, IHasLogo, IHasMugshot, ILaunchpadCelebrities, ILaunchpadUsage,
83 NotFoundError)83 NotFoundError)
84from lp.translations.interfaces.customlanguagecode import (
85 IHasCustomLanguageCodes)
84from canonical.launchpad.interfaces.launchpadstatistic import (86from canonical.launchpad.interfaces.launchpadstatistic import (
85 ILaunchpadStatisticSet)87 ILaunchpadStatisticSet)
86from lp.registry.interfaces.person import IPersonSet88from lp.registry.interfaces.person import IPersonSet
@@ -166,13 +168,13 @@
166 QuestionTargetMixin, HasTranslationImportsMixin,168 QuestionTargetMixin, HasTranslationImportsMixin,
167 HasAliasMixin, StructuralSubscriptionTargetMixin,169 HasAliasMixin, StructuralSubscriptionTargetMixin,
168 HasMilestonesMixin, OfficialBugTagTargetMixin, HasBranchesMixin,170 HasMilestonesMixin, OfficialBugTagTargetMixin, HasBranchesMixin,
169 HasMergeProposalsMixin):171 HasCustomLanguageCodesMixin, HasMergeProposalsMixin):
170172
171 """A Product."""173 """A Product."""
172174
173 implements(175 implements(
174 IFAQTarget, IHasBugSupervisor, IHasIcon, IHasLogo,176 IFAQTarget, IHasBugSupervisor, IHasCustomLanguageCodes, IHasIcon,
175 IHasMugshot, ILaunchpadUsage, IProduct, IQuestionTarget)177 IHasLogo, IHasMugshot, ILaunchpadUsage, IProduct, IQuestionTarget)
176178
177 _table = 'Product'179 _table = 'Product'
178180
@@ -964,10 +966,14 @@
964 if bug_supervisor is not None:966 if bug_supervisor is not None:
965 subscription = self.addBugSubscription(bug_supervisor, user)967 subscription = self.addBugSubscription(bug_supervisor, user)
966968
967 def getCustomLanguageCode(self, language_code):969 def composeCustomLanguageCodeMatch(self):
968 """See `IProduct`."""970 """See `HasCustomLanguageCodesMixin`."""
969 return CustomLanguageCode.selectOneBy(971 return CustomLanguageCode.product == self
970 product=self, language_code=language_code)972
973 def createCustomLanguageCode(self, language_code, language):
974 """See `IHasCustomLanguageCodes`."""
975 return CustomLanguageCode(
976 product=self, language_code=language_code, language=language)
971977
972 def userCanEdit(self, user):978 def userCanEdit(self, user):
973 """See `IProduct`."""979 """See `IProduct`."""
974980
=== modified file 'lib/lp/translations/browser/configure.zcml'
--- lib/lp/translations/browser/configure.zcml 2009-10-31 12:03:43 +0000
+++ lib/lp/translations/browser/configure.zcml 2009-11-19 04:05:29 +0000
@@ -977,5 +977,50 @@
977 name="+language-packs"977 name="+language-packs"
978 template="../templates/distroseries-language-packs.pt"/>978 template="../templates/distroseries-language-packs.pt"/>
979979
980
981<!-- CustomLanguageCode -->
982
983 <browser:defaultView
984 for="lp.translations.interfaces.customlanguagecode.ICustomLanguageCode"
985 name="+index"
986 layer="canonical.launchpad.layers.TranslationsLayer"/>
987 <browser:url
988 for="lp.translations.interfaces.customlanguagecode.ICustomLanguageCode"
989 path_expression="string:+customcode/${language_code}"
990 attribute_to_parent="translation_target"
991 />
992 <browser:page
993 name="+index"
994 for="lp.translations.interfaces.customlanguagecode.ICustomLanguageCode"
995 permission="zope.Public"
996 class="lp.translations.browser.customlanguagecode.CustomLanguageCodeView"
997 template="../templates/customlanguagecode-index.pt"
998 layer="canonical.launchpad.layers.TranslationsLayer"/>
999
1000 <browser:page
1001 name="+remove"
1002 for="lp.translations.interfaces.customlanguagecode.ICustomLanguageCode"
1003 permission="launchpad.Admin"
1004 class="lp.translations.browser.customlanguagecode.CustomLanguageCodeRemoveView"
1005 template="../../app/templates/generic-edit.pt"
1006 layer="canonical.launchpad.layers.TranslationsLayer"/>
1007
1008<!-- IHasCustomLanguageCodes -->
1009
1010 <browser:page
1011 name="+custom-language-codes"
1012 for="lp.translations.interfaces.customlanguagecode.IHasCustomLanguageCodes"
1013 layer="canonical.launchpad.layers.TranslationsLayer"
1014 class="lp.translations.browser.customlanguagecode.CustomLanguageCodesIndexView"
1015 template="../templates/customlanguagecodes-index.pt"
1016 permission="zope.Public"/>
1017 <browser:page
1018 name="+add-custom-language-code"
1019 for="lp.translations.interfaces.customlanguagecode.IHasCustomLanguageCodes"
1020 layer="canonical.launchpad.layers.TranslationsLayer"
1021 class="lp.translations.browser.customlanguagecode.CustomLanguageCodeAddView"
1022 template="../templates/customlanguagecode-add.pt"
1023 permission="launchpad.Admin"/>
1024
980 </facet>1025 </facet>
981</configure>1026</configure>
9821027
=== added file 'lib/lp/translations/browser/customlanguagecode.py'
--- lib/lp/translations/browser/customlanguagecode.py 1970-01-01 00:00:00 +0000
+++ lib/lp/translations/browser/customlanguagecode.py 2009-11-19 04:05:29 +0000
@@ -0,0 +1,171 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4__metaclass__ = type
5
6__all__ = [
7 'CustomLanguageCodeAddView',
8 'CustomLanguageCodeBreadcrumb',
9 'CustomLanguageCodesIndexView',
10 'CustomLanguageCodeRemoveView',
11 'CustomLanguageCodeView',
12 'HasCustomLanguageCodesNavigation',
13 'HasCustomLanguageCodesTraversalMixin',
14 ]
15
16
17import re
18
19from canonical.lazr.utils import smartquote
20
21from lp.translations.interfaces.customlanguagecode import (
22 ICustomLanguageCode, IHasCustomLanguageCodes)
23
24from canonical.launchpad.webapp import (
25 action, canonical_url, LaunchpadFormView, LaunchpadView, Navigation,
26 stepthrough)
27from canonical.launchpad.webapp.breadcrumb import Breadcrumb
28from canonical.launchpad.webapp.interfaces import NotFoundError
29from canonical.launchpad.webapp.menu import structured
30
31
32# Regex for allowable custom language codes.
33CODE_PATTERN = "[a-zA-Z0-9_-]+$"
34
35
36def check_code(custom_code):
37 """Is this custom language code well-formed?"""
38 return re.match(CODE_PATTERN, custom_code) is not None
39
40
41class CustomLanguageCodeBreadcrumb(Breadcrumb):
42 """Breadcrumb for a `CustomLanguageCode`."""
43 @property
44 def text(self):
45 return smartquote(
46 'Custom language code "%s"' % self.context.language_code)
47
48
49class CustomLanguageCodesIndexView(LaunchpadView):
50 """Listing of `CustomLanguageCode`s for a given context."""
51
52 page_title = "Custom language codes"
53
54 @property
55 def label(self):
56 return "Custom language codes for %s" % self.context.displayname
57
58
59class CustomLanguageCodeAddView(LaunchpadFormView):
60 """Create a new custom language code."""
61 schema = ICustomLanguageCode
62 field_names = ['language_code', 'language']
63 page_title = "Add new code"
64
65 create = False
66
67 @property
68 def label(self):
69 return (
70 "Add a custom language code for %s" % self.context.displayname)
71
72 def validate(self, data):
73 self.language_code = data.get('language_code')
74 self.language = data.get('language')
75 if self.language_code is not None:
76 self.language_code = self.language_code.strip()
77
78 if not self.language_code:
79 self.setFieldError('language_code', "No code was entered.")
80 return
81
82 if not check_code(self.language_code):
83 self.setFieldError('language_code', "Invalid language code.")
84 return
85
86 existing_code = self.context.getCustomLanguageCode(self.language_code)
87 if existing_code is not None:
88 if existing_code.language != self.language:
89 self.setFieldError(
90 'language_code',
91 structured(
92 "There already is a custom language code '%s'." %
93 self.language_code))
94 return
95 else:
96 self.create = True
97
98 @action('Add', name='add')
99 def add_action(self, action, data):
100 if self.create:
101 self.context.createCustomLanguageCode(
102 self.language_code, self.language)
103
104 @property
105 def action_url(self):
106 return "%s/+add-custom-language-code" % canonical_url(self.context)
107
108 @property
109 def next_url(self):
110 """See `LaunchpadFormView`."""
111 return "%s/+custom-language-codes" % canonical_url(self.context)
112
113 @property
114 def cancel_url(self):
115 return self.next_url
116
117
118class CustomLanguageCodeView(LaunchpadView):
119 schema = ICustomLanguageCode
120
121
122class CustomLanguageCodeRemoveView(LaunchpadFormView):
123 """View for removing a `CustomLanguageCode`."""
124 schema = ICustomLanguageCode
125 field_names = []
126
127 page_title = "Remove"
128
129 @property
130 def code(self):
131 """The custom code."""
132 return self.context.language_code
133
134 @property
135 def label(self):
136 return "Remove custom language code '%s'" % self.code
137
138 @action("Remove")
139 def remove(self, action, data):
140 """Remove this `CustomLanguageCode`."""
141 code = self.code
142 self.context.translation_target.removeCustomLanguageCode(self.context)
143 self.request.response.addInfoNotification(
144 "Removed custom language code '%s'." % code)
145
146 @property
147 def next_url(self):
148 return "%s/+custom-language-codes" % canonical_url(
149 self.context.translation_target)
150
151 @property
152 def cancel_url(self):
153 return self.next_url
154
155
156class HasCustomLanguageCodesTraversalMixin:
157 """Navigate from an `IHasCustomLanguageCodes` to a `CustomLanguageCode`.
158 """
159 @stepthrough('+customcode')
160 def traverseCustomCode(self, name):
161 """Traverse +customcode URLs."""
162 if not check_code(name):
163 raise NotFoundError("Invalid custom language code.")
164
165 return self.context.getCustomLanguageCode(name)
166
167
168class HasCustomLanguageCodesNavigation(Navigation,
169 HasCustomLanguageCodesTraversalMixin):
170 """Generic navigation for `IHasCustomLanguageCodes`."""
171 usedfor = IHasCustomLanguageCodes
0172
=== modified file 'lib/lp/translations/configure.zcml'
--- lib/lp/translations/configure.zcml 2009-09-17 14:45:59 +0000
+++ lib/lp/translations/configure.zcml 2009-11-19 04:05:29 +0000
@@ -549,6 +549,11 @@
549 interface="lp.translations.interfaces.translationmessage.ITranslationMessageSet"/>549 interface="lp.translations.interfaces.translationmessage.ITranslationMessageSet"/>
550 </securedutility>550 </securedutility>
551 </facet>551 </facet>
552 <adapter
553 provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
554 for="lp.translations.interfaces.customlanguagecode.ICustomLanguageCode"
555 factory="lp.translations.browser.customlanguagecode.CustomLanguageCodeBreadcrumb"
556 permission="zope.Public"/>
552 <class557 <class
553 class="lp.translations.model.customlanguagecode.CustomLanguageCode">558 class="lp.translations.model.customlanguagecode.CustomLanguageCode">
554 <allow559 <allow
555560
=== modified file 'lib/lp/translations/interfaces/customlanguagecode.py'
--- lib/lp/translations/interfaces/customlanguagecode.py 2009-07-17 00:26:05 +0000
+++ lib/lp/translations/interfaces/customlanguagecode.py 2009-11-19 04:05:29 +0000
@@ -1,5 +1,6 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
3# pylint: disable-msg=E0213
34
4"""Custom language code."""5"""Custom language code."""
56
@@ -7,22 +8,76 @@
78
8__all__ = [9__all__ = [
9 'ICustomLanguageCode',10 'ICustomLanguageCode',
11 'IHasCustomLanguageCodes',
10 ]12 ]
1113
12from zope.interface import Interface, Attribute14from zope.interface import Interface
13from zope.schema import Int, TextLine15from zope.schema import Bool, Choice, Int, Object, Set, TextLine
16from zope.schema.interfaces import IObject
1417
15from canonical.launchpad import _18from canonical.launchpad import _
1619
20from lp.registry.interfaces.distribution import IDistribution
21from lp.registry.interfaces.product import IProduct
22from lp.registry.interfaces.sourcepackagename import ISourcePackageName
23
1724
18class ICustomLanguageCode(Interface):25class ICustomLanguageCode(Interface):
19 """`CustomLanguageCode` interface."""26 """`CustomLanguageCode` interface."""
2027
21 id = Int(title=_(u"ID"), required=True, readonly=True)28 id = Int(title=_("ID"), required=True, readonly=True)
22 product = Attribute(_(u"Product"))29 product = Object(
23 distribution = Attribute(_(u"Distribution"))30 title=_("Product"), required=False, readonly=True, schema=IProduct)
24 sourcepackagename = Attribute(_(u"Source package name"))31 distribution = Object(
25 language_code = TextLine(title=_(u"Language code"), required=True,32 title=_("Distribution"), required=False, readonly=True,
26 description=_("Language code to treat as special."))33 schema=IDistribution)
27 language = Attribute(_(u"Language"))34 sourcepackagename = Object(
2835 title=_("Source package name"), required=False, readonly=True,
36 schema=ISourcePackageName)
37 language_code = TextLine(title=_("Language code"),
38 description=_("Language code to treat as special."),
39 required=True, readonly=False)
40 language = Choice(
41 title=_("Language"), required=False, readonly=False,
42 vocabulary='Language',
43 description=_("Language to map this code to. "
44 "Leave empty to drop translations for this code."))
45
46 # Reference back to the IHasCustomLanguageCodes.
47 translation_target = Object(
48 title=_("Context this custom language code applies to"),
49 required=True, readonly=True, schema=IObject)
50
51
52class IHasCustomLanguageCodes(Interface):
53 """A context that can have custom language codes attached.
54
55 Implemented by `Product` and `SourcePackage`.
56 """
57 custom_language_codes = Set(
58 title=_("Custom language codes"),
59 description=_("Translations for these language codes are re-routed."),
60 value_type=Object(schema=ICustomLanguageCode),
61 required=False, readonly=False)
62
63 has_custom_language_codes = Bool(
64 title=_("There are custom language codes in this context."),
65 readonly=True, required=True)
66
67 def getCustomLanguageCode(language_code):
68 """Retrieve `CustomLanguageCode` for `language_code`.
69
70 :return: a `CustomLanguageCode`, or None.
71 """
72
73 def createCustomLanguageCode(language_code, language):
74 """Create `CustomLanguageCode`.
75
76 :return: the new `CustomLanguageCode` object.
77 """
78
79 def removeCustomLanguageCode(language_code):
80 """Remove `CustomLanguageCode`.
81
82 :param language_code: A `CustomLanguageCode` object.
83 """
2984
=== modified file 'lib/lp/translations/model/customlanguagecode.py'
--- lib/lp/translations/model/customlanguagecode.py 2009-07-17 00:26:05 +0000
+++ lib/lp/translations/model/customlanguagecode.py 2009-11-19 04:05:29 +0000
@@ -5,14 +5,23 @@
55
6__metaclass__ = type6__metaclass__ = type
77
8__all__ = ['CustomLanguageCode']8__all__ = [
99 'CustomLanguageCode',
10 'HasCustomLanguageCodesMixin',
11 ]
12
13
14from zope.interface import implements
1015
11from sqlobject import ForeignKey, StringCol16from sqlobject import ForeignKey, StringCol
12from zope.interface import implements17from storm.expr import And
1318
14from canonical.database.sqlbase import SQLBase19from canonical.database.sqlbase import SQLBase
20from canonical.launchpad.interfaces.lpstorm import IStore
21
15from lp.translations.interfaces.customlanguagecode import ICustomLanguageCode22from lp.translations.interfaces.customlanguagecode import ICustomLanguageCode
23
24
16class CustomLanguageCode(SQLBase):25class CustomLanguageCode(SQLBase):
17 """See `ICustomLanguageCode`."""26 """See `ICustomLanguageCode`."""
1827
@@ -32,3 +41,63 @@
32 language = ForeignKey(41 language = ForeignKey(
33 dbName='language', foreignKey='Language', notNull=False, default=None)42 dbName='language', foreignKey='Language', notNull=False, default=None)
3443
44 @property
45 def translation_target(self):
46 """See `ICustomLanguageCode`."""
47 # Avoid circular imports
48 from lp.registry.model.distributionsourcepackage import (
49 DistributionSourcePackage)
50 if self.product:
51 return self.product
52 else:
53 return DistributionSourcePackage(
54 self.distribution, self.sourcepackagename)
55
56
57class HasCustomLanguageCodesMixin:
58 """Helper class to implement `IHasCustomLanguageCodes`."""
59
60 def composeCustomLanguageCodeMatch(self):
61 """Define in child: compose Storm match clause.
62
63 This should return a condition for use in a Storm query to match
64 `CustomLanguageCode` objects to `self`.
65 """
66 raise NotImplementedError("composeCustomLanguageCodeMatch")
67
68 def createCustomLanguageCode(self, language_code, language):
69 """Define in child. See `IHasCustomLanguageCodes`."""
70 raise NotImplementedError("createCustomLanguageCode")
71
72 def _queryCustomLanguageCodes(self, language_code=None):
73 """Query `CustomLanguageCodes` belonging to `self`.
74
75 :param language_code: Optional custom language code to look for.
76 If not given, all codes will match.
77 :return: A Storm result set.
78 """
79 match = self.composeCustomLanguageCodeMatch()
80 store = IStore(CustomLanguageCode)
81 if language_code is not None:
82 match = And(
83 match, CustomLanguageCode.language_code == language_code)
84 return store.find(CustomLanguageCode, match)
85
86 @property
87 def has_custom_language_codes(self):
88 """See `IHasCustomLanguageCodes`."""
89 return self._queryCustomLanguageCodes().any() is not None
90
91 @property
92 def custom_language_codes(self):
93 """See `IHasCustomLanguageCodes`."""
94 return self._queryCustomLanguageCodes().order_by('language_code')
95
96 def getCustomLanguageCode(self, language_code):
97 """See `IHasCustomLanguageCodes`."""
98 return self._queryCustomLanguageCodes(language_code).one()
99
100 def removeCustomLanguageCode(self, custom_code):
101 """See `IHasCustomLanguageCodes`."""
102 language_code = custom_code.language_code
103 return self._queryCustomLanguageCodes(language_code).remove()
35104
=== modified file 'lib/lp/translations/model/translationimportqueue.py'
--- lib/lp/translations/model/translationimportqueue.py 2009-11-18 11:45:59 +0000
+++ lib/lp/translations/model/translationimportqueue.py 2009-11-19 04:05:29 +0000
@@ -342,11 +342,12 @@
342 def _findCustomLanguageCode(self, language_code):342 def _findCustomLanguageCode(self, language_code):
343 """Find applicable custom language code, if any."""343 """Find applicable custom language code, if any."""
344 if self.distroseries is not None:344 if self.distroseries is not None:
345 return self.distroseries.distribution.getCustomLanguageCode(345 target = self.distroseries.distribution.getSourcePackage(
346 self.sourcepackagename, language_code)346 self.sourcepackagename)
347 else:347 else:
348 return self.productseries.product.getCustomLanguageCode(348 target = self.productseries.product
349 language_code)349
350 return target.getCustomLanguageCode(language_code)
350351
351 def _guessLanguage(self):352 def _guessLanguage(self):
352 """See ITranslationImportQueueEntry."""353 """See ITranslationImportQueueEntry."""
353354
=== added file 'lib/lp/translations/stories/standalone/custom-language-codes.txt'
--- lib/lp/translations/stories/standalone/custom-language-codes.txt 1970-01-01 00:00:00 +0000
+++ lib/lp/translations/stories/standalone/custom-language-codes.txt 2009-11-19 04:05:29 +0000
@@ -0,0 +1,276 @@
1Custom Language Codes
2---------------------
3
4Some projects insist on using nonstandard language codes, such as es_ES
5for standard Spanish or pt-BR instead of pt_BR. Custom language codes
6are a feature that helps deal with this during translation import. A
7custom language code maps a language code as the project (or package)
8uses it to a language, regardless of whether the code is for an existing
9language or not.
10
11Custom language codes are attached to either a product or a source
12package.
13
14 >>> import re
15 >>> from zope.component import getUtility
16 >>> from zope.security.proxy import removeSecurityProxy
17 >>> from canonical.launchpad.interfaces.launchpad import (
18 ... ILaunchpadCelebrities)
19
20 >>> def find_custom_language_codes_link(browser):
21 ... """Find reference to custom language codes on a page."""
22 ... return find_tag_by_id(browser.contents, 'custom-language-codes')
23
24 >>> login(ANONYMOUS)
25 >>> owner = factory.makePerson(email='o@example.com', password='test')
26 >>> rosetta_admin = factory.makePerson(
27 ... email='r@example.com', password='test')
28 >>> rosetta_admin.join(getUtility(ILaunchpadCelebrities).rosetta_experts)
29 >>> product = factory.makeProduct(displayname="Foo", owner=owner)
30 >>> trunk = product.getSeries('trunk')
31 >>> removeSecurityProxy(product).official_rosetta = True
32 >>> template = factory.makePOTemplate(productseries=trunk)
33 >>> product_page = canonical_url(product, rootsite='translations')
34 >>> logout()
35
36 >>> owner_browser = setupBrowser("Basic o@example.com:test")
37
38An administrator sees the link to the custom language codes on a
39project's main translations page.
40
41 >>> admin_browser.open(product_page)
42 >>> tag = find_custom_language_codes_link(admin_browser)
43 >>> print extract_text(tag.renderContents())
44 If necessary, you may
45 define custom language codes
46 for this project.
47
48The link goes to the custom language codes management page.
49
50 >>> admin_browser.getLink("define custom language codes").click()
51 >>> custom_language_codes_page = admin_browser.url
52
53Non-admins, even the project's owner, don't see this link. We do not
54advertise this feature, since the proper solution is generally to use
55the right language codes.
56
57 >>> owner_browser.open(product_page)
58 >>> print find_custom_language_codes_link(owner_browser)
59 None
60
61Initially the page shows no custom language codes for the project.
62
63 >>> tag = find_tag_by_id(admin_browser.contents, 'empty')
64 >>> print extract_text(tag.renderContents())
65 No custom language codes have been defined.
66
67The admin can add a custom language code.
68
69 >>> admin_browser.getLink("Add a custom language code").click()
70 >>> add_page = admin_browser.url
71
72 >>> admin_browser.getControl("Language code:").value = 'no'
73 >>> admin_browser.getControl("Language:").value = ['nn']
74 >>> admin_browser.getControl("Add").click()
75
76This leads back to the custom language codes overview, where the new
77code is now shown.
78
79 >>> admin_browser.url == custom_language_codes_page
80 True
81
82 >>> tag = find_tag_by_id(admin_browser.contents, 'nonempty')
83 >>> print extract_text(tag.renderContents())
84 Foo uses the following custom language codes:
85 Code... ...maps to language
86 no Norwegian Nynorsk
87
88There is an overview page for the custom code, though there's not much
89to see there.
90
91 >>> admin_browser.getLink("no").click()
92 >>> main = find_main_content(admin_browser.contents)
93 >>> print extract_text(main.renderContents())
94 Foo Translations Custom language code ...no...
95 For Foo, uploads with the language code
96 &ldquo;no&rdquo;
97 are associated with the language
98 Norwegian Nynorsk.
99 remove custom language code
100 custom language codes overview
101
102The overview page leads back to the custom language codes overview.
103
104 >>> code_page = admin_browser.url
105 >>> admin_browser.getLink("custom language codes overview").click()
106 >>> admin_browser.url == custom_language_codes_page
107 True
108
109 >>> admin_browser.open(code_page)
110
111There is also a link for removing codes. The admin follows the link and
112removes the "no" custom language code.
113
114 >>> admin_browser.getLink("remove custom language code").click()
115 >>> remove_page = admin_browser.url
116 >>> admin_browser.getControl("Remove").click()
117
118This leads back to the overview page.
119
120 >>> admin_browser.url == custom_language_codes_page
121 True
122
123 >>> tag = find_tag_by_id(admin_browser.contents, 'empty')
124 >>> print extract_text(tag.renderContents())
125 No custom language codes have been defined.
126
127
128Non-admin access
129================
130
131A non-admin can see the page, actually, if they know the URL. This can
132be convenient for debugging.
133
134 >>> owner_browser.open(custom_language_codes_page)
135
136 >>> tag = find_tag_by_id(owner_browser.contents, 'empty')
137 >>> print extract_text(tag.renderContents())
138 No custom language codes have been defined.
139
140However all they get is a read-only version of the page.
141
142 >>> owner_browser.getLink("Add a custom language code").click()
143 Traceback (most recent call last):
144 ...
145 LinkNotFoundError
146
147The page for adding custom language codes is not accessible to them.
148
149 >>> owner_browser.open(add_page)
150 Traceback (most recent call last):
151 ...
152 Unauthorized: ...
153
154And naturally, if an admin creates a custom language code again, a
155non-admin can't remove it.
156
157 >>> admin_browser.open(add_page)
158 >>> admin_browser.getControl("Language code:").value = 'no'
159 >>> admin_browser.getControl("Language:").value = ['nn']
160 >>> admin_browser.getControl("Add").click()
161
162 >>> owner_browser.open(custom_language_codes_page)
163 >>> tag = find_tag_by_id(owner_browser.contents, 'nonempty')
164 >>> print extract_text(tag.renderContents())
165 Foo uses the following custom language codes:
166 Code... ...maps to language
167 no Norwegian Nynorsk
168
169 >>> owner_browser.getLink("no").click()
170 >>> owner_browser.getLink("remove custom language code")
171 Traceback (most recent call last):
172 ...
173 LinkNotFoundError
174
175 >>> owner_browser.open(remove_page)
176 Traceback (most recent call last):
177 ...
178 Unauthorized: ...
179
180
181Source packages
182===============
183
184The story for source packages is very similar to that for products. In
185this case, the custom language code is tied to the distribution source
186package--i.e. the combination of a distribution and a source package
187name. However, since there is no Translations page for that type of
188object (and we'd probably never go there if there were), the link is
189shown on the source package page.
190
191 >>> login(ANONYMOUS)
192 >>> from lp.registry.model.sourcepackage import SourcePackage
193 >>> from lp.registry.model.sourcepackagename import SourcePackageName
194
195 >>> distro = factory.makeDistribution('distro')
196 >>> distroseries = factory.makeDistroRelease(distribution=distro)
197 >>> sourcepackagename = SourcePackageName(name='bar')
198 >>> package = factory.makeSourcePackage(
199 ... sourcepackagename=sourcepackagename, distroseries=distroseries)
200 >>> removeSecurityProxy(distro).official_rosetta = True
201 >>> other_series = factory.makeDistroRelease(distribution=distro)
202 >>> template = factory.makePOTemplate(
203 ... distroseries=package.distroseries,
204 ... sourcepackagename=package.sourcepackagename)
205 >>> package_page = canonical_url(package, rootsite="translations")
206 >>> page_in_other_series = canonical_url(SourcePackage(
207 ... distroseries=other_series,
208 ... sourcepackagename=package.sourcepackagename),
209 ... rootsite="translations")
210 >>> logout()
211
212 >>> admin_browser.open(package_page)
213
214Of course in this case, the notice about there being no custom language
215codes talks about a package, not a project.
216
217 >>> tag = find_custom_language_codes_link(admin_browser)
218 >>> print extract_text(tag.renderContents())
219 If necessary, you may
220 define custom language codes
221 for this package.
222
223 >>> admin_browser.getLink("define custom language codes").click()
224 >>> custom_language_codes_page = admin_browser.url
225
226 >>> tag = find_tag_by_id(admin_browser.contents, 'empty')
227 >>> print extract_text(tag.renderContents())
228 No custom language codes have been defined.
229
230Again, an admin can add a language code.
231
232 >>> admin_browser.getLink("Add a custom language code").click()
233 >>> add_page = admin_browser.url
234
235 >>> admin_browser.getControl("Language code:").value = 'pt-br'
236 >>> admin_browser.getControl("Language:").value = ['pt_BR']
237 >>> admin_browser.getControl("Add").click()
238
239The language code is displayed.
240
241 >>> tag = find_tag_by_id(admin_browser.contents, 'nonempty')
242 >>> print extract_text(tag.renderContents())
243 bar in distro uses the following custom language codes:
244 Code... ...maps to language
245 pt-br Portuguese (Brazil)
246
247It's also displayed identically on the same package but in another
248release series of the same distribution.
249
250 >>> admin_browser.open(page_in_other_series)
251 >>> tag = find_custom_language_codes_link(admin_browser)
252 >>> print extract_text(tag.renderContents())
253 If necessary, you may
254 define custom language codes
255 for this package.
256
257 >>> admin_browser.getLink("define custom language codes").click()
258 >>> tag = find_tag_by_id(admin_browser.contents, 'nonempty')
259 >>> print extract_text(tag.renderContents())
260 bar in distro uses the following custom language codes:
261 Code... ...maps to language
262 pt-br Portuguese (Brazil)
263
264
265The new code has a link there...
266
267 >>> admin_browser.getLink("pt-br").click()
268
269...and can be deleted.
270
271 >>> admin_browser.getLink("remove custom language code").click()
272 >>> admin_browser.getControl("Remove").click()
273
274 >>> tag = find_tag_by_id(admin_browser.contents, 'empty')
275 >>> print extract_text(tag.renderContents())
276 No custom language codes have been defined.
0277
=== added file 'lib/lp/translations/templates/customlanguagecode-add.pt'
--- lib/lp/translations/templates/customlanguagecode-add.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/translations/templates/customlanguagecode-add.pt 2009-11-19 04:05:29 +0000
@@ -0,0 +1,29 @@
1<html
2 xmlns="http://www.w3.org/1999/xhtml"
3 xmlns:tal="http://xml.zope.org/namespaces/tal"
4 xmlns:metal="http://xml.zope.org/namespaces/metal"
5 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
6 metal:use-macro="view/macro:page/main_only"
7 i18n:domain="launchpad">
8
9<body>
10 <div metal:fill-slot="main">
11 <div metal:use-macro="context/@@launchpad_form/form">
12 <metal:extra-info metal:fill-slot="extra_info">
13 <p>
14 Enter a language code, and select what language it should map
15 to during upload auto-approval.
16 </p>
17 <p>
18 Avoid using this capability if possible, since it makes
19 it harder to keep track of what goes where. For cases with
20 few templates, where a code would only cover one or two
21 translation files, it may be better to approve those
22 uploads manually. Launchpad will remember the filename and
23 approve it automatically next time it comes along.
24 </p>
25 </metal:extra-info>
26 </div>
27 </div>
28</body>
29</html>
030
=== added file 'lib/lp/translations/templates/customlanguagecode-index.pt'
--- lib/lp/translations/templates/customlanguagecode-index.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/translations/templates/customlanguagecode-index.pt 2009-11-19 04:05:29 +0000
@@ -0,0 +1,46 @@
1<html
2 xmlns="http://www.w3.org/1999/xhtml"
3 xmlns:tal="http://xml.zope.org/namespaces/tal"
4 xmlns:metal="http://xml.zope.org/namespaces/metal"
5 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
6 metal:use-macro="view/macro:page/main_only"
7 i18n:domain="launchpad"
8 >
9<body>
10 <div metal:fill-slot="main">
11 <div class="top-portlet">
12 For
13 <tal:target replace="context/translation_target/displayname">
14 Evolution</tal:target>,
15 uploads with the language code
16 &ldquo;<strong tal:content="context/language_code">pt-BR</strong>&rdquo;
17 <tal:language condition="context/language">
18 are associated with the language
19 <a tal:replace="structure context/language/fmt:link">
20 Brazilian Portuguese</a>.
21 </tal:language>
22 <tal:no-language condition="not: context/language">
23 are ignored.
24 </tal:no-language>
25 </div>
26
27 <div class="portlet">
28 <ul class="horizontal">
29 <li tal:condition="context/required:launchpad.Admin">
30 <a class="remove sprite"
31 tal:attributes="href context/fmt:url/+remove">
32 remove custom language code
33 </a>
34 </li>
35 <li>
36 <a class="info sprite"
37 tal:attributes="href context/translation_target/fmt:url/+custom-language-codes">
38 custom language codes overview
39 </a>
40 </li>
41 </ul>
42 </div>
43 </div>
44</body>
45</html>
46
047
=== added file 'lib/lp/translations/templates/customlanguagecodes-index.pt'
--- lib/lp/translations/templates/customlanguagecodes-index.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/translations/templates/customlanguagecodes-index.pt 2009-11-19 04:05:29 +0000
@@ -0,0 +1,80 @@
1<html
2 xmlns="http://www.w3.org/1999/xhtml"
3 xmlns:tal="http://xml.zope.org/namespaces/tal"
4 xmlns:metal="http://xml.zope.org/namespaces/metal"
5 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
6 metal:use-macro="view/macro:page/main_only"
7 i18n:domain="launchpad"
8 >
9<body>
10 <div metal:fill-slot="main">
11 <div class="top-portlet">
12 <p>
13 You can define custom language codes for
14 <tal:target replace="structure context/displayname">Evolution</tal:target>
15 here. Custom language codes will be treated like proper
16 language codes by translations imports, except each is
17 associated with a language you choose.
18 </p>
19 <p>
20 Avoid doing this if possible; it makes it harder to keep track
21 of what goes where during translations import, and why.
22 </p>
23 </div>
24 <div tal:condition="context/has_custom_language_codes"
25 class="portlet"
26 id="nonempty">
27 <p>
28 <tal:block replace="context/displayname">Evolution</tal:block>
29 uses the following custom language codes:
30 </p>
31 <table class="listing" style="max-width:800px">
32 <thead>
33 <tr>
34 <th>Code...</th>
35 <th>...maps to language</th>
36 <th tal:condition="context/required:launchpad.Admin"></th>
37 </tr>
38 </thead>
39 <tbody>
40 <tr tal:repeat="entry context/custom_language_codes">
41 <td align="center">
42 <a tal:attributes="href entry/fmt:url"
43 tal:content="entry/language_code">pt-PT</a>
44 </td>
45 <td>
46 <a tal:condition="entry/language"
47 tal:replace="structure entry/language/fmt:link">
48 Portuguese (pt)
49 </a>
50 <tal:nolanguage condition="not: entry/language">
51 &mdash;
52 </tal:nolanguage>
53 </td>
54 <td tal:condition="context/required:launchpad.Admin">
55 <a tal:attributes="href entry/fmt:url/+remove"
56 alt="Remove"
57 title="Remove"
58 class="remove sprite"></a>
59 </td>
60 </tr>
61 </tbody>
62 </table>
63 </div>
64
65 <p tal:condition="not: context/has_custom_language_codes"
66 class="portlet"
67 id="empty">
68 No custom language codes have been defined.
69 </p>
70
71 <div>
72 <a tal:attributes="href context/fmt:url/+add-custom-language-code"
73 tal:condition="context/required:launchpad.Admin"
74 class="add sprite">
75 Add a custom language code
76 </a>
77 </div>
78 </div>
79</body>
80</html>
081
=== modified file 'lib/lp/translations/templates/product-portlet-translatables.pt'
--- lib/lp/translations/templates/product-portlet-translatables.pt 2009-10-31 12:03:43 +0000
+++ lib/lp/translations/templates/product-portlet-translatables.pt 2009-11-19 04:05:29 +0000
@@ -63,4 +63,16 @@
63 </div>63 </div>
6464
65</div>65</div>
66
67<div class="portlet"
68 tal:condition="context/required:launchpad.Admin"
69 id="custom-language-codes">
70 If necessary, you may
71 <a tal:attributes="href context/fmt:url/+custom-language-codes"
72 class="edit sprite">
73 define custom language codes
74 </a>
75 for this project.
76</div>
77
66</tal:root>78</tal:root>
6779
=== modified file 'lib/lp/translations/templates/sourcepackage-translations.pt'
--- lib/lp/translations/templates/sourcepackage-translations.pt 2009-09-25 16:07:06 +0000
+++ lib/lp/translations/templates/sourcepackage-translations.pt 2009-11-19 04:05:29 +0000
@@ -39,6 +39,15 @@
39 <a tal:attributes="href context/menu:navigation/download/url">39 <a tal:attributes="href context/menu:navigation/download/url">
40 download a full tarball</a> with translations.40 download a full tarball</a> with translations.
41 </p>41 </p>
42 <p tal:condition="context/required:launchpad.Admin"
43 id="custom-language-codes">
44 If necessary, you may
45 <a tal:attributes="href context/distribution_sourcepackage/fmt:url/+custom-language-codes"
46 class="edit sprite">
47 define custom language codes
48 </a>
49 for this package.
50 </p>
42 </div>51 </div>
43 </div>52 </div>
44 </div>53 </div>
4554
=== modified file 'lib/lp/translations/tests/test_autoapproval.py'
--- lib/lp/translations/tests/test_autoapproval.py 2009-11-17 09:50:33 +0000
+++ lib/lp/translations/tests/test_autoapproval.py 2009-11-19 04:05:29 +0000
@@ -89,26 +89,23 @@
89 self.assertEqual(fresh_product.getCustomLanguageCode('pt_PT'), None)89 self.assertEqual(fresh_product.getCustomLanguageCode('pt_PT'), None)
9090
91 fresh_distro = Distribution.byName('gentoo')91 fresh_distro = Distribution.byName('gentoo')
92 nocode = fresh_distro.getCustomLanguageCode(92 gentoo_package = fresh_distro.getSourcePackage(self.sourcepackagename)
93 self.sourcepackagename, 'nocode')93 nocode = gentoo_package.getCustomLanguageCode('nocode')
94 self.assertEqual(nocode, None)94 self.assertEqual(nocode, None)
95 brazilian = fresh_distro.getCustomLanguageCode(95 brazilian = gentoo_package.getCustomLanguageCode('Brazilian')
96 self.sourcepackagename, 'Brazilian')
97 self.assertEqual(brazilian, None)96 self.assertEqual(brazilian, None)
9897
99 fresh_package = SourcePackageName.byName('cnews')98 cnews = SourcePackageName.byName('cnews')
100 self.assertEqual(self.distro.getCustomLanguageCode(99 cnews_package = self.distro.getSourcePackage(cnews)
101 fresh_package, 'nocode'), None)100 self.assertEqual(cnews_package.getCustomLanguageCode('nocode'), None)
102 self.assertEqual(self.distro.getCustomLanguageCode(101 self.assertEqual(
103 fresh_package, 'Brazilian'), None)102 cnews_package.getCustomLanguageCode('Brazilian'), None)
104103
105 def test_UnsuccessfulCustomLanguageCodeLookup(self):104 def test_UnsuccessfulCustomLanguageCodeLookup(self):
106 # Look up nonexistent custom language code for product.105 # Look up nonexistent custom language code for product.
107 self.assertEqual(self.product.getCustomLanguageCode('nocode'), None)106 self.assertEqual(self.product.getCustomLanguageCode('nocode'), None)
108 self.assertEqual(107 package = self.distro.getSourcePackage(self.sourcepackagename)
109 self.distro.getCustomLanguageCode(108 self.assertEqual(package.getCustomLanguageCode('nocode'), None)
110 self.sourcepackagename, 'nocode'),
111 None)
112109
113 def test_SuccessfulProductCustomLanguageCodeLookup(self):110 def test_SuccessfulProductCustomLanguageCodeLookup(self):
114 # Look up custom language code.111 # Look up custom language code.
@@ -122,8 +119,8 @@
122119
123 def test_SuccessfulPackageCustomLanguageCodeLookup(self):120 def test_SuccessfulPackageCustomLanguageCodeLookup(self):
124 # Look up custom language code.121 # Look up custom language code.
125 Brazilian_code = self.distro.getCustomLanguageCode(122 package = self.distro.getSourcePackage(self.sourcepackagename)
126 self.sourcepackagename, 'Brazilian')123 Brazilian_code = package.getCustomLanguageCode('Brazilian')
127 self.assertEqual(Brazilian_code, self.package_codes['Brazilian'])124 self.assertEqual(Brazilian_code, self.package_codes['Brazilian'])
128 self.assertEqual(Brazilian_code.product, None)125 self.assertEqual(Brazilian_code.product, None)
129 self.assertEqual(Brazilian_code.distribution, self.distro)126 self.assertEqual(Brazilian_code.distribution, self.distro)