collective.workspace 4.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. collective/workspace/__init__.py +22 -0
  2. collective/workspace/adapters.py +21 -0
  3. collective/workspace/browser.py +172 -0
  4. collective/workspace/catalog.py +15 -0
  5. collective/workspace/configure.zcml +117 -0
  6. collective/workspace/events.py +27 -0
  7. collective/workspace/i18n.sh +79 -0
  8. collective/workspace/interfaces.py +16 -0
  9. collective/workspace/locales/collective.workspace.pot +95 -0
  10. collective/workspace/locales/de/LC_MESSAGES/collective.workspace.po +92 -0
  11. collective/workspace/locales/de/LC_MESSAGES/plone.po +63 -0
  12. collective/workspace/locales/fi/LC_MESSAGES/collective.workspace.po +92 -0
  13. collective/workspace/locales/fi/LC_MESSAGES/plone.po +63 -0
  14. collective/workspace/locales/manual-collective.workspace.pot +14 -0
  15. collective/workspace/locales/manual-plone.pot +5 -0
  16. collective/workspace/locales/plone.pot +66 -0
  17. collective/workspace/membership.py +221 -0
  18. collective/workspace/pas.py +112 -0
  19. collective/workspace/profiles/default/actions.xml +28 -0
  20. collective/workspace/profiles/default/catalog.xml +10 -0
  21. collective/workspace/profiles/default/collective.workspace.txt +1 -0
  22. collective/workspace/profiles/default/metadata.xml +7 -0
  23. collective/workspace/profiles/default/registry.xml +29 -0
  24. collective/workspace/profiles/default/rolemap.xml +9 -0
  25. collective/workspace/setuphandlers.py +21 -0
  26. collective/workspace/templates/WorkspaceGroupManagerForm.zpt +85 -0
  27. collective/workspace/templates/team_roster.pt +108 -0
  28. collective/workspace/templates/team_roster_row.pt +34 -0
  29. collective/workspace/testing.py +66 -0
  30. collective/workspace/tests/__init__.py +0 -0
  31. collective/workspace/tests/test_example.py +22 -0
  32. collective/workspace/tests/test_robot.py +18 -0
  33. collective/workspace/tests/test_workspace.py +242 -0
  34. collective/workspace/tests/workspace.robot +68 -0
  35. collective/workspace/vocabs.py +30 -0
  36. collective/workspace/workspace.py +211 -0
  37. collective.workspace-4.0.0-py3.14-nspkg.pth +1 -0
  38. collective_workspace-4.0.0.dist-info/METADATA +263 -0
  39. collective_workspace-4.0.0.dist-info/RECORD +43 -0
  40. collective_workspace-4.0.0.dist-info/WHEEL +5 -0
  41. collective_workspace-4.0.0.dist-info/entry_points.txt +2 -0
  42. collective_workspace-4.0.0.dist-info/namespace_packages.txt +1 -0
  43. collective_workspace-4.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,22 @@
1
+ from AccessControl.Permissions import add_user_folders
2
+ from collective.workspace import pas
3
+ from Products.PluggableAuthService import registerMultiPlugin
4
+ from zope.i18nmessageid import MessageFactory
5
+
6
+ workspaceMessageFactory = MessageFactory("collective.workspace")
7
+
8
+
9
+ registerMultiPlugin(pas.WorkspaceGroupManager.meta_type)
10
+
11
+
12
+ def initialize(context):
13
+
14
+ context.registerClass(
15
+ pas.WorkspaceGroupManager,
16
+ permission=add_user_folders,
17
+ constructors=(
18
+ pas.manage_addWorkspaceGroupManagerForm,
19
+ pas.addWorkspaceGroupManager,
20
+ ),
21
+ visibility=None,
22
+ )
@@ -0,0 +1,21 @@
1
+ from collective.workspace.interfaces import IHasWorkspace
2
+ from plone import api
3
+ from plone.app.dexterity.permissions import DXFieldPermissionChecker
4
+ from zope.component import adapter
5
+
6
+
7
+ @adapter(IHasWorkspace)
8
+ class WorkspaceFieldPermissionChecker(DXFieldPermissionChecker):
9
+ def validate(self, field_name, vocabulary_name=None):
10
+ """Override the permission checker to allow querying the users
11
+ vocabulary even if there is no user field in this context
12
+ """
13
+ if (
14
+ field_name == "user"
15
+ and vocabulary_name == "plone.app.vocabularies.Users"
16
+ and api.user.has_permission(
17
+ "collective.workspace: Manage roster", obj=self.context
18
+ )
19
+ ):
20
+ return True
21
+ return super().validate(field_name, vocabulary_name)
@@ -0,0 +1,172 @@
1
+ from AccessControl import getSecurityManager
2
+ from collections import namedtuple
3
+ from collective.workspace import workspaceMessageFactory as _
4
+ from collective.workspace.interfaces import IRosterView
5
+ from collective.workspace.interfaces import IWorkspace
6
+ from plone.autoform.base import AutoFields
7
+ from plone.autoform.form import AutoExtensibleForm
8
+ from plone.z3cform import z2
9
+ from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
10
+ from Products.statusmessages.interfaces import IStatusMessage
11
+ from z3c.form import button
12
+ from z3c.form.form import DisplayForm
13
+ from z3c.form.form import EditForm
14
+ from z3c.form.interfaces import ActionExecutionError
15
+ from zope.cachedescriptors.property import Lazy as lazy_property
16
+ from zope.interface import implementer
17
+ from zope.publisher.interfaces.browser import IPublishTraverse
18
+
19
+ import transaction
20
+
21
+ Action = namedtuple("Action", "label view_name permission")
22
+
23
+
24
+ @implementer(IRosterView)
25
+ class TeamRosterView(AutoFields, DisplayForm):
26
+ """Display the roster as a table."""
27
+
28
+ row_template = ViewPageTemplateFile("templates/team_roster_row.pt")
29
+
30
+ row_actions = (
31
+ Action("Edit", "edit-roster", "collective.workspace: Manage roster"),
32
+ )
33
+
34
+ table_actions = ()
35
+
36
+ def __init__(self, context, request):
37
+ self.context = context
38
+ self.request = request
39
+ self.workspace = IWorkspace(self.context)
40
+
41
+ @property
42
+ def label(self):
43
+ return _("Roster: ${title}", mapping={"title": self.context.Title()})
44
+
45
+ @lazy_property
46
+ def schema(self):
47
+ return self.workspace.membership_schema
48
+
49
+ ignoreContext = True
50
+
51
+ def getContent(self):
52
+ return self.context
53
+
54
+ def row_views(self):
55
+ for membership in self.workspace:
56
+ for widget in self.widgets.values():
57
+ widget.ignoreContext = False
58
+ widget.context = membership
59
+ widget.update()
60
+ yield self.row_template(membership=membership)
61
+
62
+ @lazy_property
63
+ def can_edit_roster(self):
64
+ return getSecurityManager().checkPermission(
65
+ "collective.workspace: Manage roster", self.context
66
+ )
67
+
68
+ def update(self):
69
+ z2.switch_on(self)
70
+ self.updateFieldsFromSchemata()
71
+ self.updateWidgets()
72
+
73
+ def render(self):
74
+ return self.index()
75
+
76
+ def __call__(self):
77
+ self.update()
78
+ return self.render()
79
+
80
+
81
+ @implementer(IPublishTraverse)
82
+ class TeamMemberEditForm(AutoExtensibleForm, EditForm):
83
+ def __init__(self, context, request):
84
+ self.context = context
85
+ self.request = request
86
+ self.workspace = IWorkspace(self.context)
87
+
88
+ key = None
89
+
90
+ def publishTraverse(self, request, name):
91
+ self.key = name
92
+ return self
93
+
94
+ @lazy_property
95
+ def schema(self):
96
+ return self.workspace.membership_schema
97
+
98
+ def updateFields(self):
99
+ super().updateFields()
100
+ # don't show the user field if we are editing
101
+ if self.key:
102
+ del self.fields["user"]
103
+
104
+ @lazy_property
105
+ def ignoreContext(self):
106
+ return not bool(self.key)
107
+
108
+ @lazy_property
109
+ def label(self):
110
+ if self.key:
111
+ return self.getContent()._title
112
+ else:
113
+ return _("Add Person to Roster")
114
+
115
+ @lazy_property
116
+ def _content(self):
117
+ if not self.key:
118
+ return self.context
119
+ workspace = self.workspace
120
+ memberdata = workspace.members[self.key]
121
+ return workspace.membership_factory(workspace, memberdata)
122
+
123
+ def getContent(self):
124
+ return self._content
125
+
126
+ def validateInvariants(self, membership):
127
+ pass
128
+
129
+ @button.buttonAndHandler(_("Save"))
130
+ def handleSave(self, action):
131
+ data, errors = self.extractData()
132
+ if errors:
133
+ self.status = self.formErrorsMessage
134
+ return
135
+
136
+ status = _("Changes saved")
137
+
138
+ if self.key:
139
+ membership = self.getContent()
140
+ membership.update(data)
141
+ else:
142
+ # Add new roster member
143
+ membership = self.workspace.add_to_team(**data)
144
+ status = _("User added")
145
+
146
+ try:
147
+ self.validateInvariants(membership)
148
+ except ActionExecutionError:
149
+ # make sure above changes won't be persisted
150
+ transaction.abort()
151
+ raise
152
+
153
+ self._finished = True
154
+ IStatusMessage(self.request).addStatusMessage(status, "info")
155
+
156
+ @property
157
+ def can_remove(self):
158
+ return self.key
159
+
160
+ @button.buttonAndHandler(_("Remove"), condition=lambda self: self.can_remove)
161
+ def handleRemove(self, action):
162
+ membership = self.getContent()
163
+ membership.remove_from_team()
164
+ self._finished = True
165
+ IStatusMessage(self.request).addStatusMessage(_("User removed"), "info")
166
+
167
+ _finished = False
168
+
169
+ def render(self):
170
+ if self._finished:
171
+ return " "
172
+ return super().render()
@@ -0,0 +1,15 @@
1
+ from .interfaces import IHasWorkspace
2
+ from .interfaces import IWorkspace
3
+ from plone.indexer import indexer
4
+ from zope.interface import Interface
5
+
6
+
7
+ @indexer(IHasWorkspace)
8
+ def workspace_members(self):
9
+ workspace = IWorkspace(self)
10
+ return set(workspace.members or [])
11
+
12
+
13
+ @indexer(Interface)
14
+ def null_indexer(self):
15
+ raise AttributeError
@@ -0,0 +1,117 @@
1
+ <configure
2
+ xmlns="http://namespaces.zope.org/zope"
3
+ xmlns:browser="http://namespaces.zope.org/browser"
4
+ xmlns:five="http://namespaces.zope.org/five"
5
+ xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
6
+ xmlns:i18n="http://namespaces.zope.org/i18n"
7
+ xmlns:plone="http://namespaces.plone.org/plone"
8
+ i18n_domain="collective.workspace"
9
+ >
10
+
11
+ <five:registerPackage
12
+ package="."
13
+ initialize=".initialize"
14
+ />
15
+ <i18n:registerTranslations directory="locales" />
16
+
17
+ <include package="plone.app.dexterity" />
18
+
19
+ <permission
20
+ id="collective.workspace.ViewRoster"
21
+ title="collective.workspace: View roster"
22
+ >
23
+ <role name="TeamMember" />
24
+ <role name="TeamGuest" />
25
+ <role name="TeamManager" />
26
+ <role name="Site Administrator" />
27
+ <role name="Manager" />
28
+ <role name="Owner" />
29
+ </permission>
30
+
31
+ <permission
32
+ id="collective.workspace.ManageRoster"
33
+ title="collective.workspace: Manage roster"
34
+ >
35
+ <role name="TeamManager" />
36
+ <role name="Site Administrator" />
37
+ <role name="Manager" />
38
+ <role name="Owner" />
39
+ </permission>
40
+
41
+ <genericsetup:registerProfile
42
+ name="default"
43
+ title="collective.workspace"
44
+ description="Installs the collective.workspace package"
45
+ provides="Products.GenericSetup.interfaces.EXTENSION"
46
+ directory="profiles/default"
47
+ />
48
+
49
+ <genericsetup:importStep
50
+ name="collective.workspace_various"
51
+ title="collective.workspace various"
52
+ description=""
53
+ handler=".setuphandlers.setup_pas"
54
+ />
55
+
56
+ <plone:behavior
57
+ name="collective.workspace.team_workspace"
58
+ title="Team workspace"
59
+ description="This item is a team workspace."
60
+ factory=".workspace.Workspace"
61
+ provides=".interfaces.IWorkspace"
62
+ for="plone.dexterity.interfaces.IDexterityContent"
63
+ marker=".interfaces.IHasWorkspace"
64
+ />
65
+
66
+ <browser:page
67
+ name="team-roster"
68
+ for=".interfaces.IHasWorkspace"
69
+ class=".browser.TeamRosterView"
70
+ template="templates/team_roster.pt"
71
+ permission="collective.workspace.ViewRoster"
72
+ />
73
+
74
+ <browser:page
75
+ name="edit-roster"
76
+ for=".interfaces.IHasWorkspace"
77
+ class=".browser.TeamMemberEditForm"
78
+ permission="collective.workspace.ManageRoster"
79
+ />
80
+
81
+ <utility
82
+ name="collective.workspace.groups"
83
+ component=".vocabs.TeamGroupsVocabulary"
84
+ />
85
+
86
+ <adapter factory=".adapters.WorkspaceFieldPermissionChecker" />
87
+
88
+ <adapter
89
+ factory=".pas.WorkspaceRoles"
90
+ for=".interfaces.IHasWorkspace"
91
+ name="collective.workspace"
92
+ />
93
+
94
+ <adapter factory=".membership.workspace_from_membership" />
95
+
96
+ <utility
97
+ factory=".pas.TeamManagerRoleDelegation"
98
+ provides="plone.app.workflow.interfaces.ISharingPageRole"
99
+ name="TeamManager"
100
+ />
101
+
102
+ <adapter
103
+ factory=".catalog.workspace_members"
104
+ name="workspace_members"
105
+ />
106
+ <adapter
107
+ factory=".catalog.null_indexer"
108
+ name="workspace_members"
109
+ />
110
+
111
+ <subscriber handler=".workspace.handle_workspace_added" />
112
+ <subscriber handler=".workspace.handle_workspace_modified" />
113
+ <subscriber handler=".workspace.handle_workspace_removed" />
114
+ <subscriber handler=".workspace.handle_workspace_copied" />
115
+ <subscriber handler=".workspace.handle_principal_deleted" />
116
+
117
+ </configure>
@@ -0,0 +1,27 @@
1
+ from zope.interface.interfaces import ObjectEvent
2
+
3
+
4
+ class TeamMemberEvent(ObjectEvent):
5
+ """Base for team member events.
6
+
7
+ This is used instead of normal Zope object lifecycle
8
+ events so it can be dispatched based on the workspace
9
+ rather than based on the modified object (which is
10
+ just a dictionary in this case).
11
+ """
12
+
13
+ def __init__(self, object, membership):
14
+ self.object = object
15
+ self.membership = membership
16
+
17
+
18
+ class TeamMemberAddedEvent(TeamMemberEvent):
19
+ """Event for when a member is added."""
20
+
21
+
22
+ class TeamMemberRemovedEvent(TeamMemberEvent):
23
+ """Event for when a member is removed."""
24
+
25
+
26
+ class TeamMemberModifiedEvent(TeamMemberEvent):
27
+ """Event for when a member is modified."""
@@ -0,0 +1,79 @@
1
+ #!/bin/sh
2
+ #
3
+ # Shell script to manage .po files.
4
+ #
5
+ # Run this file in the folder main __init__.py of product
6
+ #
7
+ # E.g. if your product is yourproduct.name
8
+ # you run this file in yourproduct.name/yourproduct/name
9
+ #
10
+ #
11
+ # Copyright 2010 mFabrik http://mfabrik.com
12
+ #
13
+ # http://plone.org/documentation/manual/plone-community-developer-documentation/i18n/localization
14
+ #
15
+
16
+ # Assume the product name is the current folder name
17
+ CURRENT_PATH=`pwd`
18
+ CATALOGNAMES="collective.workspace plone"
19
+
20
+ # List of languages
21
+ LANGUAGES="de"
22
+
23
+ # Create locales folder structure for languages
24
+ install -d locales
25
+ for lang in $LANGUAGES; do
26
+ install -d locales/$lang/LC_MESSAGES
27
+ done
28
+
29
+ I18NDUDE=~/Plone/zinstance/bin/i18ndude
30
+
31
+ if test ! -e $I18NDUDE; then
32
+ echo "You must install i18ndude with buildout"
33
+ echo "See https://github.com/collective/collective.developermanual/blob/master/source/i18n/localization.txt"
34
+ exit
35
+ fi
36
+
37
+ #
38
+ # Do we need to merge manual PO entries from a file called manual.pot.
39
+ # this option is later passed to i18ndude
40
+ #
41
+
42
+ for catalogname in $CATALOGNAMES; do
43
+ echo "Catalog: ${catalogname}"
44
+ if test -e locales/manual-${catalogname}.pot; then
45
+ echo "Manual PO entries detected"
46
+ MERGE="--merge locales/manual-${catalogname}.pot"
47
+ else
48
+ echo "No manual PO entries detected"
49
+ MERGE=""
50
+ fi
51
+ $I18NDUDE rebuild-pot --pot locales/$catalogname.pot $MERGE --create $catalogname .
52
+ echo ""
53
+ done
54
+
55
+ # Rebuild .pot
56
+
57
+
58
+ # Compile po files
59
+ for lang in $(find locales -mindepth 1 -maxdepth 1 -type d); do
60
+
61
+ if test -d $lang/LC_MESSAGES; then
62
+
63
+ for catalogname in $CATALOGNAMES; do
64
+
65
+ PO=$lang/LC_MESSAGES/${catalogname}.po
66
+
67
+ # Create po file if not exists
68
+ touch $PO
69
+
70
+ # Sync po file
71
+ echo "Syncing $PO"
72
+ $I18NDUDE sync --pot locales/$catalogname.pot $PO
73
+ echo ""
74
+
75
+ done
76
+ fi
77
+
78
+ done
79
+ #!/bin/sh
@@ -0,0 +1,16 @@
1
+ from zope.i18nmessageid import MessageFactory
2
+ from zope.interface import Interface
3
+
4
+ _ = MessageFactory("collective.workspace")
5
+
6
+
7
+ class IHasWorkspace(Interface):
8
+ """Marker for team workspaces."""
9
+
10
+
11
+ class IWorkspace(Interface):
12
+ """Interface for interacting with an item's workspace features."""
13
+
14
+
15
+ class IRosterView(Interface):
16
+ """Marker for display view of a roster."""
@@ -0,0 +1,95 @@
1
+ # --- PLEASE EDIT THE LINES BELOW CORRECTLY ---
2
+ # SOME DESCRIPTIVE TITLE.
3
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
4
+ msgid ""
5
+ msgstr ""
6
+ "Project-Id-Version: PACKAGE VERSION\n"
7
+ "POT-Creation-Date: 2017-06-14 09:07+0000\n"
8
+ "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
9
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
10
+ "Language-Team: LANGUAGE <LL@li.org>\n"
11
+ "MIME-Version: 1.0\n"
12
+ "Content-Type: text/plain; charset=utf-8\n"
13
+ "Content-Transfer-Encoding: 8bit\n"
14
+ "Plural-Forms: nplurals=1; plural=0\n"
15
+ "Language-Code: en\n"
16
+ "Language-Name: English\n"
17
+ "Preferred-Encodings: utf-8 latin1\n"
18
+ "Domain: DOMAIN\n"
19
+
20
+ #: collective/workspace/templates/team_roster.pt:15
21
+ msgid "&#43; Add person to roster"
22
+ msgstr ""
23
+
24
+ #: collective/workspace/browser.py:119
25
+ msgid "Add Person to Roster"
26
+ msgstr ""
27
+
28
+ msgid "Admins"
29
+ msgstr ""
30
+
31
+ #: collective/workspace/pas.py:333
32
+ msgid "Can edit roster"
33
+ msgstr ""
34
+
35
+ #: collective/workspace/browser.py:141
36
+ msgid "Changes saved"
37
+ msgstr ""
38
+
39
+ msgid "Edit"
40
+ msgstr ""
41
+
42
+ #: collective/workspace/membership.py:37
43
+ msgid "Groups"
44
+ msgstr ""
45
+
46
+ msgid "Guests"
47
+ msgstr ""
48
+
49
+ #: collective/workspace/configure.zcml:42
50
+ msgid "Installs the collective.workspace package"
51
+ msgstr ""
52
+
53
+ #: collective/workspace/membership.py:31
54
+ msgid "Position"
55
+ msgstr ""
56
+
57
+ #: collective/workspace/browser.py:166
58
+ msgid "Remove"
59
+ msgstr ""
60
+
61
+ #: collective/workspace/browser.py:44
62
+ msgid "Roster: ${title}"
63
+ msgstr ""
64
+
65
+ #: collective/workspace/browser.py:135
66
+ msgid "Save"
67
+ msgstr ""
68
+
69
+ #: collective/workspace/templates/team_roster.pt:46
70
+ msgid "Selected people:"
71
+ msgstr ""
72
+
73
+ #: collective/workspace/configure.zcml:57
74
+ msgid "Team workspace"
75
+ msgstr ""
76
+
77
+ #: collective/workspace/configure.zcml:57
78
+ msgid "This item is a team workspace."
79
+ msgstr ""
80
+
81
+ #: collective/workspace/membership.py:26
82
+ msgid "User"
83
+ msgstr ""
84
+
85
+ #: collective/workspace/browser.py:149
86
+ msgid "User added"
87
+ msgstr ""
88
+
89
+ #: collective/workspace/browser.py:172
90
+ msgid "User removed"
91
+ msgstr ""
92
+
93
+ #: collective/workspace/configure.zcml:42
94
+ msgid "collective.workspace"
95
+ msgstr ""
@@ -0,0 +1,92 @@
1
+ msgid ""
2
+ msgstr ""
3
+ "Project-Id-Version: PACKAGE VERSION\n"
4
+ "POT-Creation-Date: 2017-06-14 09:07+0000\n"
5
+ "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
6
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
7
+ "Language-Team: LANGUAGE <LL@li.org>\n"
8
+ "MIME-Version: 1.0\n"
9
+ "Content-Type: text/plain; charset=utf-8\n"
10
+ "Content-Transfer-Encoding: 8bit\n"
11
+ "Plural-Forms: nplurals=1; plural=0\n"
12
+ "Language-Code: en\n"
13
+ "Language-Name: English\n"
14
+ "Preferred-Encodings: utf-8 latin1\n"
15
+ "Domain: DOMAIN\n"
16
+
17
+ #: collective/workspace/templates/team_roster.pt:15
18
+ msgid "&#43; Add person to roster"
19
+ msgstr "&#43; Person zum Team hinzufügen"
20
+
21
+ #: collective/workspace/browser.py:119
22
+ msgid "Add Person to Roster"
23
+ msgstr "Eine Person zum Team hinzufügen"
24
+
25
+ msgid "Admins"
26
+ msgstr ""
27
+
28
+ #: collective/workspace/pas.py:333
29
+ msgid "Can edit roster"
30
+ msgstr "Kann Team bearbeiten"
31
+
32
+ #: collective/workspace/browser.py:141
33
+ msgid "Changes saved"
34
+ msgstr "Änderungen gespeichert"
35
+
36
+ msgid "Edit"
37
+ msgstr "Bearbeiten"
38
+
39
+ #: collective/workspace/membership.py:37
40
+ msgid "Groups"
41
+ msgstr "Gruppen"
42
+
43
+ msgid "Guests"
44
+ msgstr ""
45
+
46
+ #: collective/workspace/configure.zcml:42
47
+ msgid "Installs the collective.workspace package"
48
+ msgstr ""
49
+
50
+ #: collective/workspace/membership.py:31
51
+ msgid "Position"
52
+ msgstr "Position"
53
+
54
+ #: collective/workspace/browser.py:166
55
+ msgid "Remove"
56
+ msgstr "Entfernen"
57
+
58
+ #: collective/workspace/browser.py:44
59
+ msgid "Roster: ${title}"
60
+ msgstr "Teamverwaltung: ${title}"
61
+
62
+ #: collective/workspace/browser.py:135
63
+ msgid "Save"
64
+ msgstr "Speichern"
65
+
66
+ #: collective/workspace/templates/team_roster.pt:46
67
+ msgid "Selected people:"
68
+ msgstr "Ausgewählte Personen"
69
+
70
+ #: collective/workspace/configure.zcml:57
71
+ msgid "Team workspace"
72
+ msgstr ""
73
+
74
+ #: collective/workspace/configure.zcml:57
75
+ msgid "This item is a team workspace."
76
+ msgstr ""
77
+
78
+ #: collective/workspace/membership.py:26
79
+ msgid "User"
80
+ msgstr "Benutzer"
81
+
82
+ #: collective/workspace/browser.py:149
83
+ msgid "User added"
84
+ msgstr ""
85
+
86
+ #: collective/workspace/browser.py:172
87
+ msgid "User removed"
88
+ msgstr "Benutzer entfernt"
89
+
90
+ #: collective/workspace/configure.zcml:42
91
+ msgid "collective.workspace"
92
+ msgstr ""