[gen] Allow to use class Group as parameter of Search.group (soon, we will be able to get groups of groups of groups... of searches, to produce a tree of searches); refactored i18n-related code.

This commit is contained in:
Gaetan Delannay 2012-11-06 11:32:39 +01:00
parent fcb1d36da0
commit 0dd870c042
6 changed files with 324 additions and 560 deletions

View file

@ -5,7 +5,6 @@ import re, time, copy, sys, types, os, os.path, mimetypes, string, StringIO, \
from appy import Object from appy import Object
from appy.gen.layout import Table from appy.gen.layout import Table
from appy.gen.layout import defaultFieldLayouts from appy.gen.layout import defaultFieldLayouts
from appy.gen.po import PoMessage
from appy.gen.mail import sendNotification from appy.gen.mail import sendNotification
from appy.gen.indexer import defaultIndexes, XhtmlTextExtractor from appy.gen.indexer import defaultIndexes, XhtmlTextExtractor
from appy.gen.utils import GroupDescr, Keywords, getClassName, SomeObjects from appy.gen.utils import GroupDescr, Keywords, getClassName, SomeObjects
@ -191,7 +190,6 @@ class Group:
# (b) groupData is of the form <groupName>_<numberOfColumns>. # (b) groupData is of the form <groupName>_<numberOfColumns>.
groupElems = groupData.rsplit('_', 1) groupElems = groupData.rsplit('_', 1)
if len(groupElems) == 1: if len(groupElems) == 1:
# We have case (a)
res = Group(groupElems[0]) res = Group(groupElems[0])
else: else:
try: try:
@ -208,36 +206,29 @@ class Group:
if self.master: return (self.master, self.masterValue) if self.master: return (self.master, self.masterValue)
if self.group: return self.group.getMasterData() if self.group: return self.group.getMasterData()
def generateLabels(self, messages, classDescr, walkedGroups): def generateLabels(self, messages, classDescr, walkedGroups,
forSearch=False):
'''This method allows to generate all the needed i18n labels related to '''This method allows to generate all the needed i18n labels related to
this group. p_messages is the list of i18n p_messages that we are this group. p_messages is the list of i18n p_messages (a PoMessages
currently building; p_classDescr is the descriptor of the class where instance) that we are currently building; p_classDescr is the
this group is defined.''' descriptor of the class where this group is defined. If p_forSearch
is True, this group is used for grouping searches, and not fields.'''
# A part of the group label depends on p_forSearch.
if forSearch: gp = 'searchgroup'
else: gp = 'group'
if self.hasLabel: if self.hasLabel:
msgId = '%s_group_%s' % (classDescr.name, self.name) msgId = '%s_%s_%s' % (classDescr.name, gp, self.name)
poMsg = PoMessage(msgId, '', self.name, niceDefault=True) messages.append(msgId, self.name)
if poMsg not in messages:
messages.append(poMsg)
classDescr.labelsToPropagate.append(poMsg)
if self.hasDescr: if self.hasDescr:
msgId = '%s_group_%s_descr' % (classDescr.name, self.name) msgId = '%s_%s_%s_descr' % (classDescr.name, gp, self.name)
poMsg = PoMessage(msgId, '', ' ') messages.append(msgId, ' ', nice=False)
if poMsg not in messages:
messages.append(poMsg)
classDescr.labelsToPropagate.append(poMsg)
if self.hasHelp: if self.hasHelp:
msgId = '%s_group_%s_help' % (classDescr.name, self.name) msgId = '%s_%s_%s_help' % (classDescr.name, gp, self.name)
poMsg = PoMessage(msgId, '', ' ') messages.append(msgId, ' ', nice=False)
if poMsg not in messages:
messages.append(poMsg)
classDescr.labelsToPropagate.append(poMsg)
if self.hasHeaders: if self.hasHeaders:
for i in range(self.nbOfHeaders): for i in range(self.nbOfHeaders):
msgId = '%s_group_%s_col%d' % (classDescr.name, self.name, i+1) msgId = '%s_%s_%s_col%d' % (classDescr.name, gp, self.name, i+1)
poMsg = PoMessage(msgId, '', ' ') messages.append(msgId, ' ', nice=False)
if poMsg not in messages:
messages.append(poMsg)
classDescr.labelsToPropagate.append(poMsg)
walkedGroups.add(self) walkedGroups.add(self)
if self.group and (self.group not in walkedGroups) and \ if self.group and (self.group not in walkedGroups) and \
not self.group.label: not self.group.label:
@ -302,7 +293,8 @@ class Search:
def __init__(self, name, group=None, sortBy='', sortOrder='asc', limit=None, def __init__(self, name, group=None, sortBy='', sortOrder='asc', limit=None,
default=False, **fields): default=False, **fields):
self.name = name self.name = name
self.group = group # Searches may be visually grouped in the portlet # Searches may be visually grouped in the portlet.
self.group = Group.get(group)
self.sortBy = sortBy self.sortBy = sortBy
self.sortOrder = sortOrder self.sortOrder = sortOrder
self.limit = limit self.limit = limit
@ -905,24 +897,6 @@ class Type:
p_self type definition on p_obj.''' p_self type definition on p_obj.'''
setattr(obj, self.name, value) setattr(obj, self.name, value)
def clone(self, forTool=True):
'''Returns a clone of myself. If p_forTool is True, the clone will be
adapted to its life into the tool.'''
res = copy.copy(self)
res.group = copy.copy(self.group)
res.page = copy.copy(self.page)
if not forTool: return res
res.show = True
# Set default layouts for all Tool fields
res.layouts = res.formatLayouts(None)
res.specificReadPermission = False
res.specificWritePermission = False
res.multiplicity = (0, self.multiplicity[1])
if callable(res.validator):
# We will not be able to call this function from the tool.
res.validator = None
return res
def callMethod(self, obj, method, raiseOnError=True): def callMethod(self, obj, method, raiseOnError=True):
'''This method is used to call a p_method on p_obj. p_method is part of '''This method is used to call a p_method on p_obj. p_method is part of
this type definition (ie a default method, the method of a Computed this type definition (ie a default method, the method of a Computed
@ -2025,18 +1999,6 @@ class Ref(Type):
if objects: if objects:
self.linkObject(obj, objects) self.linkObject(obj, objects)
def clone(self, forTool=True):
'''Produces a clone of myself.'''
res = Type.clone(self, forTool)
res.back = copy.copy(self.back)
if not forTool: return res
res.link = True
res.add = False
res.back.attribute += 'DefaultValue'
res.back.show = False
res.select = None # Not callable from tool.
return res
def mayAdd(self, obj): def mayAdd(self, obj):
'''May the user create a new referred object from p_obj via this Ref?''' '''May the user create a new referred object from p_obj via this Ref?'''
# We can't (yet) do that on back references. # We can't (yet) do that on back references.

View file

@ -5,7 +5,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import types, copy import types, copy
import appy.gen as gen import appy.gen as gen
from po import PoMessage import po
from model import ModelClass, toolFieldPrefixes from model import ModelClass, toolFieldPrefixes
from utils import produceNiceMessage, getClassName from utils import produceNiceMessage, getClassName
TABS = 4 # Number of blanks in a Python indentation. TABS = 4 # Number of blanks in a Python indentation.
@ -29,20 +29,6 @@ class ClassDescriptor(Descriptor):
def __init__(self, klass, orderedAttributes, generator): def __init__(self, klass, orderedAttributes, generator):
Descriptor.__init__(self, klass, orderedAttributes, generator) Descriptor.__init__(self, klass, orderedAttributes, generator)
self.methods = '' # Needed method definitions will be generated here self.methods = '' # Needed method definitions will be generated here
# We remember here encountered pages and groups defined in the Appy
# type. Indeed, after having parsed all application classes, we will
# need to generate i18n labels for every child class of the class
# that declared pages and groups.
self.labelsToPropagate = [] #~[PoMessage]~ Some labels (like page,
# group or action names) need to be propagated in children classes
# (because they contain the class name). But at this time we don't know
# yet every sub-class. So we store those labels here; the Generator
# will propagate them later.
self.toolFieldsToPropagate = [] # For this class, some fields have
# been defined on the Tool class. Those fields need to be defined
# for child classes of this class as well, but at this time we don't
# know yet every sub-class. So we store field definitions here; the
# Generator will propagate them later.
self.name = getClassName(self.klass, generator.applicationName) self.name = getClassName(self.klass, generator.applicationName)
self.predefined = False self.predefined = False
self.customized = False self.customized = False
@ -280,6 +266,10 @@ class FieldDescriptor:
self.fieldType = None self.fieldType = None
self.widgetType = None self.widgetType = None
def i18n(self, id, default, nice=True):
'''Shorthand for adding a new message into self.generator.labels.'''
self.generator.labels.append(id, default, nice=nice)
def __repr__(self): def __repr__(self):
return '<Field %s, %s>' % (self.fieldName, self.classDescr) return '<Field %s, %s>' % (self.fieldName, self.classDescr)
@ -291,7 +281,7 @@ class FieldDescriptor:
fullPrefix = prefix + 'For' fullPrefix = prefix + 'For'
if fieldName.startswith(fullPrefix): if fieldName.startswith(fullPrefix):
messageId = 'MSG_%s' % prefix messageId = 'MSG_%s' % prefix
res = getattr(PoMessage, messageId) res = getattr(po, messageId)
if res.find('%s') != -1: if res.find('%s') != -1:
# I must complete the message with the field name. # I must complete the message with the field name.
res = res % fieldName.split('_')[-1] res = res % fieldName.split('_')[-1]
@ -302,18 +292,15 @@ class FieldDescriptor:
'''Gets the default label, description or help (depending on p_msgType) '''Gets the default label, description or help (depending on p_msgType)
for i18n message p_msgId.''' for i18n message p_msgId.'''
default = ' ' default = ' '
produceNice = False niceDefault = False
if isLabel: if isLabel:
produceNice = True niceDefault = True
default = self.fieldName default = self.fieldName
# Some attributes need a specific predefined message # Some attributes need a specific predefined message
if isinstance(self.classDescr, ToolClassDescriptor): if isinstance(self.classDescr, ToolClassDescriptor):
default = self.getToolFieldMessage(self.fieldName) default = self.getToolFieldMessage(self.fieldName)
if default != self.fieldName: produceNice = False if default != self.fieldName: niceDefault = False
msg = PoMessage(msgId, '', default) return msgId, default, niceDefault
if produceNice:
msg.produceNiceDefault()
return msg
def walkString(self): def walkString(self):
'''How to generate an Appy String?''' '''How to generate an Appy String?'''
@ -322,18 +309,15 @@ class FieldDescriptor:
# Generate i18n messages for every possible value if the list # Generate i18n messages for every possible value if the list
# of values is fixed. # of values is fixed.
for value in self.appyType.validator: for value in self.appyType.validator:
msgLabel = '%s_%s_list_%s' % (self.classDescr.name, label = '%s_%s_list_%s' % (self.classDescr.name,
self.fieldName, value) self.fieldName, value)
poMsg = PoMessage(msgLabel, '', value) self.i18n(label, value)
poMsg.produceNiceDefault()
self.generator.labels.append(poMsg)
def walkAction(self): def walkAction(self):
'''Generates the i18n-related label.''' '''Generates the i18n-related label.'''
if self.appyType.confirm: if self.appyType.confirm:
label = '%s_%s_confirm' % (self.classDescr.name, self.fieldName) label = '%s_%s_confirm' % (self.classDescr.name, self.fieldName)
msg = PoMessage(label, '', PoMessage.CONFIRM) self.i18n(label, po.CONFIRM, nice=False)
self.generator.labels.append(msg)
def walkRef(self): def walkRef(self):
'''How to generate a Ref?''' '''How to generate a Ref?'''
@ -343,23 +327,18 @@ class FieldDescriptor:
back = self.appyType.back back = self.appyType.back
refClassName = getClassName(self.appyType.klass, self.applicationName) refClassName = getClassName(self.appyType.klass, self.applicationName)
if back.hasLabel: if back.hasLabel:
backLabel = "%s_%s" % (refClassName, self.appyType.back.attribute) backName = self.appyType.back.attribute
poMsg = PoMessage(backLabel, '', self.appyType.back.attribute) self.i18n('%s_%s' % (refClassName, backName), backName)
poMsg.produceNiceDefault()
self.generator.labels.append(poMsg)
# Add the label for the confirm message if relevant # Add the label for the confirm message if relevant
if self.appyType.addConfirm: if self.appyType.addConfirm:
label = '%s_%s_addConfirm' % (self.classDescr.name, self.fieldName) label = '%s_%s_addConfirm' % (self.classDescr.name, self.fieldName)
msg = PoMessage(label, '', PoMessage.CONFIRM) self.i18n(label, po.CONFIRM, nice=False)
self.generator.labels.append(msg)
def walkPod(self): def walkPod(self):
# Add i18n-specific messages # Add i18n-specific messages
if self.appyType.askAction: if self.appyType.askAction:
label = '%s_%s_askaction' % (self.classDescr.name, self.fieldName) label = '%s_%s_askaction' % (self.classDescr.name, self.fieldName)
msg = PoMessage(label, '', PoMessage.POD_ASKACTION) self.i18n(label, po.POD_ASKACTION, nice=False)
self.generator.labels.append(msg)
self.classDescr.labelsToPropagate.append(msg)
# Add the POD-related fields on the Tool # Add the POD-related fields on the Tool
self.generator.tool.addPodRelatedFields(self) self.generator.tool.addPodRelatedFields(self)
@ -367,9 +346,7 @@ class FieldDescriptor:
# Add i18n-specific messages # Add i18n-specific messages
for name, field in self.appyType.fields: for name, field in self.appyType.fields:
label = '%s_%s_%s' % (self.classDescr.name, self.fieldName, name) label = '%s_%s_%s' % (self.classDescr.name, self.fieldName, name)
msg = PoMessage(label, '', name) self.i18n(label, name)
msg.produceNiceDefault()
self.generator.labels.append(msg)
def walkCalendar(self): def walkCalendar(self):
# Add i18n-specific messages # Add i18n-specific messages
@ -377,9 +354,7 @@ class FieldDescriptor:
if not isinstance(eTypes, list) and not isinstance(eTypes, tuple):return if not isinstance(eTypes, list) and not isinstance(eTypes, tuple):return
for et in self.appyType.eventTypes: for et in self.appyType.eventTypes:
label = '%s_%s_event_%s' % (self.classDescr.name,self.fieldName,et) label = '%s_%s_event_%s' % (self.classDescr.name,self.fieldName,et)
msg = PoMessage(label, '', et) self.i18n(label, et)
msg.produceNiceDefault()
self.generator.labels.append(msg)
def walkAppyType(self): def walkAppyType(self):
'''Walks into the Appy type definition and gathers data about the '''Walks into the Appy type definition and gathers data about the
@ -389,39 +364,33 @@ class FieldDescriptor:
if self.appyType.indexed and (self.fieldName != 'title'): if self.appyType.indexed and (self.fieldName != 'title'):
self.classDescr.addIndexMethod(self) self.classDescr.addIndexMethod(self)
# i18n labels # i18n labels
messages = self.generator.labels
if not self.appyType.label: if not self.appyType.label:
# Create labels for generating them in i18n files, only if required. # Create labels for generating them in i18n files, only if required.
i18nPrefix = "%s_%s" % (self.classDescr.name, self.fieldName) i18nPrefix = '%s_%s' % (self.classDescr.name, self.fieldName)
if self.appyType.hasLabel: if self.appyType.hasLabel:
messages.append(self.produceMessage(i18nPrefix)) self.i18n(*self.produceMessage(i18nPrefix))
if self.appyType.hasDescr: if self.appyType.hasDescr:
descrId = i18nPrefix + '_descr' descrId = i18nPrefix + '_descr'
messages.append(self.produceMessage(descrId,isLabel=False)) self.i18n(*self.produceMessage(descrId,isLabel=False))
if self.appyType.hasHelp: if self.appyType.hasHelp:
helpId = i18nPrefix + '_help' helpId = i18nPrefix + '_help'
messages.append(self.produceMessage(helpId, isLabel=False)) self.i18n(*self.produceMessage(helpId, isLabel=False))
# Create i18n messages linked to pages and phases, only if there is more # Create i18n messages linked to pages and phases, only if there is more
# than one page/phase for the class. # than one page/phase for the class.
ppMsgs = []
if len(self.classDescr.getPhases()) > 1: if len(self.classDescr.getPhases()) > 1:
# Create the message for the name of the phase # Create the message for the name of the phase
phaseName = self.appyType.page.phase phaseName = self.appyType.page.phase
msgId = '%s_phase_%s' % (self.classDescr.name, phaseName) msgId = '%s_phase_%s' % (self.classDescr.name, phaseName)
ppMsgs.append(PoMessage(msgId, '', produceNiceMessage(phaseName))) self.i18n(msgId, phaseName)
if len(self.classDescr.getPages()) > 1: if len(self.classDescr.getPages()) > 1:
# Create the message for the name of the page # Create the message for the name of the page
pageName = self.appyType.page.name pageName = self.appyType.page.name
msgId = '%s_page_%s' % (self.classDescr.name, pageName) msgId = '%s_page_%s' % (self.classDescr.name, pageName)
ppMsgs.append(PoMessage(msgId, '', produceNiceMessage(pageName))) self.i18n(msgId, pageName)
for poMsg in ppMsgs:
if poMsg not in messages:
messages.append(poMsg)
self.classDescr.labelsToPropagate.append(poMsg)
# Create i18n messages linked to groups # Create i18n messages linked to groups
group = self.appyType.group group = self.appyType.group
if group and not group.label: if group and not group.label:
group.generateLabels(messages, self.classDescr, set()) group.generateLabels(self.generator.labels, self.classDescr, set())
# Manage things which are specific to String types # Manage things which are specific to String types
if self.appyType.type == 'String': self.walkString() if self.appyType.type == 'String': self.walkString()
# Manage things which are specific to Actions # Manage things which are specific to Actions

View file

@ -3,9 +3,9 @@ import os, os.path, re, sys, parser, symbol, token, types
import appy.pod, appy.pod.renderer import appy.pod, appy.pod.renderer
from appy.shared.utils import FolderDeleter from appy.shared.utils import FolderDeleter
import appy.gen as gen import appy.gen as gen
from po import PoMessage, PoFile, PoParser import po
from descriptors import * from descriptors import *
from utils import produceNiceMessage, getClassName from utils import getClassName
from model import ModelClass, User, Group, Tool, Translation, Page from model import ModelClass, User, Group, Tool, Translation, Page
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -365,9 +365,13 @@ class ZopeGenerator(Generator):
self.translation = TranslationClassDescriptor(Translation, self) self.translation = TranslationClassDescriptor(Translation, self)
self.page = PageClassDescriptor(Page, self) self.page = PageClassDescriptor(Page, self)
# i18n labels to generate # i18n labels to generate
self.labels = [] # i18n labels self.labels = po.PoMessages()
self.referers = {} self.referers = {}
def i18n(self, id, default, nice=True):
'''Shorthand for adding a new message into self.labels.'''
self.labels.append(id, default, nice=nice)
versionRex = re.compile('(.*?\s+build)\s+(\d+)') versionRex = re.compile('(.*?\s+build)\s+(\d+)')
def initialize(self): def initialize(self):
# Determine version number # Determine version number
@ -387,175 +391,20 @@ class ZopeGenerator(Generator):
for fileName in os.listdir(i18nFolder): for fileName in os.listdir(i18nFolder):
name, ext = os.path.splitext(fileName) name, ext = os.path.splitext(fileName)
if ext in self.poExtensions: if ext in self.poExtensions:
poParser = PoParser(os.path.join(i18nFolder, fileName)) poParser = po.PoParser(os.path.join(i18nFolder, fileName))
self.i18nFiles[fileName] = poParser.parse() self.i18nFiles[fileName] = poParser.parse()
def finalize(self): def finalize(self):
# Some useful aliases # Add a label for the application name
msg = PoMessage self.i18n(self.applicationName, self.applicationName)
app = self.applicationName # Add default Appy i18n messages
# Some global i18n messages for id, default in po.appyLabels:
poMsg = msg(app, '', app); poMsg.produceNiceDefault() self.i18n(id, default, nice=False)
self.labels += [poMsg, # Add a label for every role added by this application (we ensure role
msg('app_name', '', msg.APP_NAME), # 'Manager' was added even if not mentioned anywhere).
msg('workflow_state', '', msg.WORKFLOW_STATE), self.i18n('role_Manager', 'Manager')
msg('appy_title', '', msg.APPY_TITLE),
msg('data_change', '', msg.DATA_CHANGE),
msg('modified_field', '', msg.MODIFIED_FIELD),
msg('previous_value', '', msg.PREVIOUS_VALUE),
msg('phase', '', msg.PHASE),
msg('workflow_comment', '', msg.WORKFLOW_COMMENT),
msg('choose_a_value', '', msg.CHOOSE_A_VALUE),
msg('choose_a_doc', '', msg.CHOOSE_A_DOC),
msg('min_ref_violated', '', msg.MIN_REF_VIOLATED),
msg('max_ref_violated', '', msg.MAX_REF_VIOLATED),
msg('no_ref', '', msg.REF_NO),
msg('add_ref', '', msg.REF_ADD),
msg('action_ok', '', msg.ACTION_OK),
msg('action_ko', '', msg.ACTION_KO),
msg('move_up', '', msg.REF_MOVE_UP),
msg('move_down', '', msg.REF_MOVE_DOWN),
msg('query_create', '', msg.QUERY_CREATE),
msg('query_import', '', msg.QUERY_IMPORT),
msg('query_no_result', '', msg.QUERY_NO_RESULT),
msg('query_consult_all', '', msg.QUERY_CONSULT_ALL),
msg('import_title', '', msg.IMPORT_TITLE),
msg('import_show_hide', '', msg.IMPORT_SHOW_HIDE),
msg('import_already', '', msg.IMPORT_ALREADY),
msg('import_many', '', msg.IMPORT_MANY),
msg('import_done', '', msg.IMPORT_DONE),
msg('search_title', '', msg.SEARCH_TITLE),
msg('search_button', '', msg.SEARCH_BUTTON),
msg('search_objects', '', msg.SEARCH_OBJECTS),
msg('search_results', '', msg.SEARCH_RESULTS),
msg('search_results_descr', '', ' '),
msg('search_new', '', msg.SEARCH_NEW),
msg('search_from', '', msg.SEARCH_FROM),
msg('search_to', '', msg.SEARCH_TO),
msg('search_or', '', msg.SEARCH_OR),
msg('search_and', '', msg.SEARCH_AND),
msg('ref_invalid_index', '', msg.REF_INVALID_INDEX),
msg('bad_long', '', msg.BAD_LONG),
msg('bad_float', '', msg.BAD_FLOAT),
msg('bad_date', '', msg.BAD_DATE),
msg('bad_email', '', msg.BAD_EMAIL),
msg('bad_url', '', msg.BAD_URL),
msg('bad_alphanumeric', '', msg.BAD_ALPHANUMERIC),
msg('bad_select_value', '', msg.BAD_SELECT_VALUE),
msg('select_delesect', '', msg.SELECT_DESELECT),
msg('no_elem_selected', '', msg.NO_SELECTION),
msg('object_edit', '', msg.EDIT),
msg('object_delete', '', msg.DELETE),
msg('object_unlink', '', msg.UNLINK),
msg('delete_confirm', '', msg.DELETE_CONFIRM),
msg('unlink_confirm', '', msg.UNLINK_CONFIRM),
msg('delete_done', '', msg.DELETE_DONE),
msg('unlink_done', '', msg.UNLINK_DONE),
msg('goto_first', '', msg.GOTO_FIRST),
msg('goto_previous', '', msg.GOTO_PREVIOUS),
msg('goto_next', '', msg.GOTO_NEXT),
msg('goto_last', '', msg.GOTO_LAST),
msg('goto_source', '', msg.GOTO_SOURCE),
msg('whatever', '', msg.WHATEVER),
msg('yes', '', msg.YES),
msg('no', '', msg.NO),
msg('field_required', '', msg.FIELD_REQUIRED),
msg('field_invalid', '', msg.FIELD_INVALID),
msg('file_required', '', msg.FILE_REQUIRED),
msg('image_required', '', msg.IMAGE_REQUIRED),
msg('odt', '', msg.FORMAT_ODT),
msg('pdf', '', msg.FORMAT_PDF),
msg('doc', '', msg.FORMAT_DOC),
msg('rtf', '', msg.FORMAT_RTF),
msg('front_page_text', '', msg.FRONT_PAGE_TEXT),
msg('captcha_text', '', msg.CAPTCHA_TEXT),
msg('bad_captcha', '', msg.BAD_CAPTCHA),
msg('app_login', '', msg.LOGIN),
msg('app_connect', '', msg.CONNECT),
msg('app_logout', '', msg.LOGOUT),
msg('app_password', '', msg.PASSWORD),
msg('app_home', '', msg.HOME),
msg('login_reserved', '', msg.LOGIN_RESERVED),
msg('login_in_use', '', msg.LOGIN_IN_USE),
msg('login_ko', '', msg.LOGIN_KO),
msg('login_ok', '', msg.LOGIN_OK),
msg('password_too_short', '', msg.PASSWORD_TOO_SHORT),
msg('passwords_mismatch', '', msg.PASSWORDS_MISMATCH),
msg('object_save', '', msg.SAVE),
msg('object_saved', '', msg.SAVED),
msg('validation_error', '', msg.ERROR),
msg('object_cancel', '', msg.CANCEL),
msg('object_canceled', '', msg.CANCELED),
msg('enable_cookies', '', msg.ENABLE_COOKIES),
msg('page_previous', '', msg.PAGE_PREVIOUS),
msg('page_next', '', msg.PAGE_NEXT),
msg('forgot_password', '', msg.FORGOT_PASSWORD),
msg('ask_password_reinit', '', msg.ASK_PASSWORD_REINIT),
msg('wrong_password_reinit','', msg.WRONG_PASSWORD_REINIT),
msg('reinit_mail_sent', '', msg.REINIT_MAIL_SENT),
msg('reinit_password', '', msg.REINIT_PASSWORD),
msg('reinit_password_body', '', msg.REINIT_PASSWORD_BODY),
msg('new_password', '', msg.NEW_PASSWORD),
msg('new_password_body', '', msg.NEW_PASSWORD_BODY),
msg('new_password_sent', '', msg.NEW_PASSWORD_SENT),
msg('last_user_access', '', msg.LAST_USER_ACCESS),
msg('object_history', '', msg.OBJECT_HISTORY),
msg('object_created_by', '', msg.OBJECT_CREATED_BY),
msg('object_created_on', '', msg.OBJECT_CREATED_ON),
msg('object_modified_on', '', msg.OBJECT_MODIFIED_ON),
msg('object_action', '', msg.OBJECT_ACTION),
msg('object_author', '', msg.OBJECT_AUTHOR),
msg('action_date', '', msg.ACTION_DATE),
msg('action_comment', '', msg.ACTION_COMMENT),
msg('day_Mon_short', '', msg.DAY_MON_SHORT),
msg('day_Tue_short', '', msg.DAY_TUE_SHORT),
msg('day_Wed_short', '', msg.DAY_WED_SHORT),
msg('day_Thu_short', '', msg.DAY_THU_SHORT),
msg('day_Fri_short', '', msg.DAY_FRI_SHORT),
msg('day_Sat_short', '', msg.DAY_SAT_SHORT),
msg('day_Sun_short', '', msg.DAY_SUN_SHORT),
msg('day_Mon', '', msg.DAY_MON),
msg('day_Tue', '', msg.DAY_TUE),
msg('day_Wed', '', msg.DAY_WED),
msg('day_Thu', '', msg.DAY_THU),
msg('day_Fri', '', msg.DAY_FRI),
msg('day_Sat', '', msg.DAY_SAT),
msg('day_Sun', '', msg.DAY_SUN),
msg('ampm_am', '', msg.AMPM_AM),
msg('ampm_pm', '', msg.AMPM_PM),
msg('month_Jan_short', '', msg.MONTH_JAN_SHORT),
msg('month_Feb_short', '', msg.MONTH_FEB_SHORT),
msg('month_Mar_short', '', msg.MONTH_MAR_SHORT),
msg('month_Apr_short', '', msg.MONTH_APR_SHORT),
msg('month_May_short', '', msg.MONTH_MAY_SHORT),
msg('month_Jun_short', '', msg.MONTH_JUN_SHORT),
msg('month_Jul_short', '', msg.MONTH_JUL_SHORT),
msg('month_Aug_short', '', msg.MONTH_AUG_SHORT),
msg('month_Sep_short', '', msg.MONTH_SEP_SHORT),
msg('month_Oct_short', '', msg.MONTH_OCT_SHORT),
msg('month_Nov_short', '', msg.MONTH_NOV_SHORT),
msg('month_Dec_short', '', msg.MONTH_DEC_SHORT),
msg('month_Jan', '', msg.MONTH_JAN),
msg('month_Feb', '', msg.MONTH_FEB),
msg('month_Mar', '', msg.MONTH_MAR),
msg('month_Apr', '', msg.MONTH_APR),
msg('month_May', '', msg.MONTH_MAY),
msg('month_Jun', '', msg.MONTH_JUN),
msg('month_Jul', '', msg.MONTH_JUL),
msg('month_Aug', '', msg.MONTH_AUG),
msg('month_Sep', '', msg.MONTH_SEP),
msg('month_Oct', '', msg.MONTH_OCT),
msg('month_Nov', '', msg.MONTH_NOV),
msg('month_Dec', '', msg.MONTH_DEC),
msg('today', '', msg.TODAY),
msg('which_event', '', msg.WHICH_EVENT),
msg('event_span', '', msg.EVENT_SPAN),
msg('del_next_events', '', msg.DEL_NEXT_EVENTS),
]
# Create a label for every role added by this application
for role in self.getAllUsedRoles(): for role in self.getAllUsedRoles():
self.labels.append(msg('role_%s' % role.name,'', role.name, self.i18n('role_%s' % role.name, role.name)
niceDefault=True))
# Create basic files (config.py, etc) # Create basic files (config.py, etc)
self.generateTool() self.generateTool()
self.generateInit() self.generateInit()
@ -570,23 +419,16 @@ class ZopeGenerator(Generator):
f = open(initFile, 'w') f = open(initFile, 'w')
f.write('') f.write('')
f.close() f.close()
# Decline i18n labels into versions for child classes
for classDescr in self.classes:
for poMsg in classDescr.labelsToPropagate:
for childDescr in classDescr.getChildren():
childMsg = poMsg.clone(classDescr.name, childDescr.name)
if childMsg not in self.labels:
self.labels.append(childMsg)
# Generate i18n pot file # Generate i18n pot file
potFileName = '%s.pot' % self.applicationName potFileName = '%s.pot' % self.applicationName
if self.i18nFiles.has_key(potFileName): if self.i18nFiles.has_key(potFileName):
potFile = self.i18nFiles[potFileName] potFile = self.i18nFiles[potFileName]
else: else:
fullName = os.path.join(self.application, 'tr', potFileName) fullName = os.path.join(self.application, 'tr', potFileName)
potFile = PoFile(fullName) potFile = po.PoFile(fullName)
self.i18nFiles[potFileName] = potFile self.i18nFiles[potFileName] = potFile
# We update the POT file with our list of automatically managed labels. # We update the POT file with our list of automatically managed labels.
removedLabels = potFile.update(self.labels, self.options.i18nClean, removedLabels = potFile.update(self.labels.get(),self.options.i18nClean,
not self.options.i18nSort) not self.options.i18nSort)
if removedLabels: if removedLabels:
print 'Warning: %d messages were removed from translation ' \ print 'Warning: %d messages were removed from translation ' \
@ -599,7 +441,7 @@ class ZopeGenerator(Generator):
nbOfPages = int(len(potFile.messages)/self.config.translationsPerPage)+1 nbOfPages = int(len(potFile.messages)/self.config.translationsPerPage)+1
for i in range(nbOfPages): for i in range(nbOfPages):
msgId = '%s_page_%d' % (self.translation.name, i+2) msgId = '%s_page_%d' % (self.translation.name, i+2)
pageLabels.append(msg(msgId, '', 'Page %d' % (i+2))) pageLabels.append(po.PoMessage(msgId, '', 'Page %d' % (i+2)))
potFile.update(pageLabels, keepExistingOrder=False) potFile.update(pageLabels, keepExistingOrder=False)
potFile.generate() potFile.generate()
# Generate i18n po files # Generate i18n po files
@ -611,7 +453,7 @@ class ZopeGenerator(Generator):
poFile = self.i18nFiles[poFileName] poFile = self.i18nFiles[poFileName]
else: else:
fullName = os.path.join(self.application, 'tr', poFileName) fullName = os.path.join(self.application, 'tr', poFileName)
poFile = PoFile(fullName) poFile = po.PoFile(fullName)
self.i18nFiles[poFileName] = poFile self.i18nFiles[poFileName] = poFile
poFile.update(potFile.messages, self.options.i18nClean, poFile.update(potFile.messages, self.options.i18nClean,
not self.options.i18nSort) not self.options.i18nSort)
@ -874,11 +716,8 @@ class ZopeGenerator(Generator):
def generateTool(self): def generateTool(self):
'''Generates the tool that corresponds to this application.''' '''Generates the tool that corresponds to this application.'''
Msg = PoMessage
# Create Tool-related i18n-related messages # Create Tool-related i18n-related messages
msg = Msg(self.tool.name, '', Msg.CONFIG % self.applicationName) self.i18n(self.tool.name, po.CONFIG % self.applicationName, nice=False)
self.labels.append(msg)
# Tune the Ref field between Tool->User and Group->User # Tune the Ref field between Tool->User and Group->User
Tool.users.klass = User Tool.users.klass = User
if self.user.customized: if self.user.customized:
@ -889,8 +728,8 @@ class ZopeGenerator(Generator):
for klass in (self.user, self.group, self.translation, self.page): for klass in (self.user, self.group, self.translation, self.page):
klassType = klass.name[len(self.applicationName):] klassType = klass.name[len(self.applicationName):]
klass.generateSchema() klass.generateSchema()
self.labels += [ Msg(klass.name, '', klassType), self.i18n(klass.name, klassType, nice=False)
Msg('%s_plural' % klass.name,'', klass.name+'s')] self.i18n('%s_plural' % klass.name, klass.name+'s', nice=False)
repls = self.repls.copy() repls = self.repls.copy()
if klass.isFolder(): if klass.isFolder():
parents = 'BaseMixin, Folder' parents = 'BaseMixin, Folder'
@ -904,14 +743,9 @@ class ZopeGenerator(Generator):
self.copyFile('Class.pyt', repls, destName='%s.py' % klass.name) self.copyFile('Class.pyt', repls, destName='%s.py' % klass.name)
# Before generating the Tool class, finalize it with query result # Before generating the Tool class, finalize it with query result
# columns, with fields to propagate, workflow-related fields. # columns, search-related and import-related fields.
for classDescr in self.getClasses(include='allButTool'): for classDescr in self.getClasses(include='allButTool'):
for fieldName, fieldType in classDescr.toolFieldsToPropagate: if not classDescr.isRoot(): continue
for childDescr in classDescr.getChildren():
childFieldName = fieldName % childDescr.name
fieldType.group = childDescr.klass.__name__
self.tool.addField(childFieldName, fieldType)
if classDescr.isRoot():
# We must be able to configure query results from the tool. # We must be able to configure query results from the tool.
self.tool.addQueryResultColumns(classDescr) self.tool.addQueryResultColumns(classDescr)
# Add the search-related fields. # Add the search-related fields.
@ -939,37 +773,27 @@ class ZopeGenerator(Generator):
baseClass = isFolder and 'Folder' or 'SimpleItem' baseClass = isFolder and 'Folder' or 'SimpleItem'
icon = isFolder and 'folder.gif' or 'object.gif' icon = isFolder and 'folder.gif' or 'object.gif'
parents = 'BaseMixin, %s' % baseClass parents = 'BaseMixin, %s' % baseClass
classDoc = classDescr.klass.__doc__ or 'Appy class.' classDoc = k.__doc__ or 'Appy class.'
repls = self.repls.copy() repls = self.repls.copy()
classDescr.generateSchema() classDescr.generateSchema()
repls.update({ repls.update({
'parents': parents, 'className': classDescr.klass.__name__, 'parents': parents, 'className': k.__name__,
'genClassName': classDescr.name, 'baseMixin':'BaseMixin', 'genClassName': classDescr.name, 'baseMixin':'BaseMixin',
'classDoc': classDoc, 'applicationName': self.applicationName, 'classDoc': classDoc, 'applicationName': self.applicationName,
'methods': classDescr.methods, 'icon':icon}) 'methods': classDescr.methods, 'icon':icon})
fileName = '%s.py' % classDescr.name fileName = '%s.py' % classDescr.name
# Create i18n labels (class name and plural form) # Create i18n labels (class name and plural form)
poMsg = PoMessage(classDescr.name, '', classDescr.klass.__name__) self.i18n(classDescr.name, k.__name__)
poMsg.produceNiceDefault() self.i18n('%s_plural' % classDescr.name, k.__name__+'s')
self.labels.append(poMsg)
poMsgPl = PoMessage('%s_plural' % classDescr.name, '',
classDescr.klass.__name__+'s')
poMsgPl.produceNiceDefault()
self.labels.append(poMsgPl)
# Create i18n labels for searches # Create i18n labels for searches
for search in classDescr.getSearches(classDescr.klass): for search in classDescr.getSearches(k):
searchLabel = '%s_search_%s' % (classDescr.name, search.name) label = '%s_search_%s' % (classDescr.name, search.name)
labels = [searchLabel, '%s_descr' % searchLabel] self.i18n(label, search.name)
if search.group: self.i18n('%s_descr' % label, ' ', nice=False)
grpLabel = '%s_searchgroup_%s' % (classDescr.name, search.group) # Generate labels for groups of searches
labels += [grpLabel, '%s_descr' % grpLabel] if search.group and not search.group.label:
for label in labels: search.group.generateLabels(self.labels, classDescr, set(),
default = ' ' forSearch=True)
if label == searchLabel: default = search.name
poMsg = PoMessage(label, '', default)
poMsg.produceNiceDefault()
if poMsg not in self.labels:
self.labels.append(poMsg)
# Generate the resulting Zope class. # Generate the resulting Zope class.
self.copyFile('Class.pyt', repls, destName=fileName) self.copyFile('Class.pyt', repls, destName=fileName)
@ -983,31 +807,23 @@ class ZopeGenerator(Generator):
# Add i18n messages for states # Add i18n messages for states
for name in dir(wfDescr.klass): for name in dir(wfDescr.klass):
if not isinstance(getattr(wfDescr.klass, name), gen.State): continue if not isinstance(getattr(wfDescr.klass, name), gen.State): continue
poMsg = PoMessage('%s_%s' % (wfName, name), '', name) self.i18n('%s_%s' % (wfName, name), name)
poMsg.produceNiceDefault()
self.labels.append(poMsg)
# Add i18n messages for transitions # Add i18n messages for transitions
for name in dir(wfDescr.klass): for name in dir(wfDescr.klass):
transition = getattr(wfDescr.klass, name) transition = getattr(wfDescr.klass, name)
if not isinstance(transition, gen.Transition): continue if not isinstance(transition, gen.Transition): continue
if transition.show: if transition.show:
poMsg = PoMessage('%s_%s' % (wfName, name), '', name) self.i18n('%s_%s' % (wfName, name), name)
poMsg.produceNiceDefault()
self.labels.append(poMsg)
if transition.show and transition.confirm: if transition.show and transition.confirm:
# We need to generate a label for the message that will be shown # We need to generate a label for the message that will be shown
# in the confirm popup. # in the confirm popup.
label = '%s_%s_confirm' % (wfName, name) self.i18n('%s_%s_confirm'%(wfName, name),po.CONFIRM, nice=False)
poMsg = PoMessage(label, '', PoMessage.CONFIRM)
self.labels.append(poMsg)
if transition.notify: if transition.notify:
# Appy will send a mail when this transition is triggered. # Appy will send a mail when this transition is triggered.
# So we need 2 i18n labels: one for the mail subject and one for # So we need 2 i18n labels: one for the mail subject and one for
# the mail body. # the mail body.
subjectLabel = '%s_%s_mail_subject' % (wfName, name) self.i18n('%s_%s_mail_subject' % (wfName, name),
poMsg = PoMessage(subjectLabel, '', PoMessage.EMAIL_SUBJECT) po.EMAIL_SUBJECT, nice=False)
self.labels.append(poMsg) self.i18n('%s_%s_mail_body' % (wfName, name),
bodyLabel = '%s_%s_mail_body' % (wfName, name) po.EMAIL_BODY, nice=False)
poMsg = PoMessage(bodyLabel, '', PoMessage.EMAIL_BODY)
self.labels.append(poMsg)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -127,7 +127,7 @@ class Table(LayoutElement):
# We need to create a Table instance from another Table instance, # We need to create a Table instance from another Table instance,
# given in p_other. In this case, we ignore previous params. # given in p_other. In this case, we ignore previous params.
if derivedType != None: if derivedType != None:
# We will not simply clone p_other. If p_derivedType is: # We will not simply mimic p_other. If p_derivedType is:
# - "view", p_derivedFrom is an "edit" layout, and we must # - "view", p_derivedFrom is an "edit" layout, and we must
# create the corresponding "view" layout; # create the corresponding "view" layout;
# - "cell", p_derivedFrom is a "view" layout, and we must # - "cell", p_derivedFrom is a "view" layout, and we must

View file

@ -675,7 +675,7 @@ class ToolMixin(BaseMixin):
def getSearches(self, contentType): def getSearches(self, contentType):
'''Returns an object with 2 attributes: '''Returns an object with 2 attributes:
* "searches" stores the searches that are defined for p_contentType; * "searches" stores the searches that are defined for p_contentType;
* "default" stores the search defined as default one. * "default" stores the search defined as the default one.
Every item representing a search is a dict containing info about a Every item representing a search is a dict containing info about a
search or about a group of searches. search or about a group of searches.
''' '''
@ -685,25 +685,27 @@ class ToolMixin(BaseMixin):
visitedGroups = {} # Names of already visited search groups visitedGroups = {} # Names of already visited search groups
for search in ClassDescriptor.getSearches(appyClass): for search in ClassDescriptor.getSearches(appyClass):
# Determine first group label, we will need it. # Determine first group label, we will need it.
groupLabel = '' groupLabel = None
groupName = None
if search.group: if search.group:
groupLabel = '%s_searchgroup_%s' % (contentType, search.group) groupName = search.group.name
groupLabel = '%s_searchgroup_%s' % (contentType, groupName)
# Add an item representing the search group if relevant # Add an item representing the search group if relevant
if search.group and (search.group not in visitedGroups): if groupName and (groupName not in visitedGroups):
group = {'name': search.group, 'isGroup': True, group = {'name': groupName, 'isGroup': True,
'labelId': groupLabel, 'searches': [], 'labelId': groupLabel, 'searches': [],
'label': self.translate(groupLabel), 'label': self.translate(groupLabel),
'descr': self.translate('%s_descr' % groupLabel), 'descr': self.translate('%s_descr' % groupLabel),
} }
searches.append(group) searches.append(group)
visitedGroups[search.group] = group visitedGroups[groupName] = group
# Add the search itself # Add the search itself
searchLabel = '%s_search_%s' % (contentType, search.name) searchLabel = '%s_search_%s' % (contentType, search.name)
dSearch = {'name': search.name, 'isGroup': False, dSearch = {'name': search.name, 'isGroup': False,
'label': self.translate(searchLabel), 'label': self.translate(searchLabel),
'descr': self.translate('%s_descr' % searchLabel)} 'descr': self.translate('%s_descr' % searchLabel)}
if search.group: if groupName:
visitedGroups[search.group]['searches'].append(dSearch) visitedGroups[groupName]['searches'].append(dSearch)
else: else:
searches.append(dSearch) searches.append(dSearch)
if search.default: if search.default:

395
gen/po.py
View file

@ -21,121 +21,106 @@ msgstr ""
fallbacks = {'en': 'en-us en-ca', fallbacks = {'en': 'en-us en-ca',
'fr': 'fr-be fr-ca fr-lu fr-mc fr-ch fr-fr'} 'fr': 'fr-be fr-ca fr-lu fr-mc fr-ch fr-fr'}
# ------------------------------------------------------------------------------ # Standard Appy labels with their default value in english ---------------------
class PoMessage: appyLabels = [
'''Represents a i18n message (po format).''' ('app_name', 'Appy'),
CONFIG = "Configuration panel for product '%s'" ('workflow_state', 'state'),
APP_NAME = "Appy" ('workflow_comment', 'Optional comment'),
# The following messages (starting with MSG_) correspond to tool ('appy_title', 'Title'),
# attributes added for every gen-class (warning: the message IDs correspond ('data_change', 'Data change'),
# to MSG_<attributePrefix>). ('modified_field', 'Modified field'),
MSG_podTemplate = "POD template for field '%s'" ('previous_value', 'Previous value'),
MSG_formats = "Output format(s) for field '%s'" ('phase', 'phase'),
MSG_resultColumns = "Columns to display while showing query results" ('choose_a_value', ' - '),
MSG_enableAdvancedSearch = "Enable advanced search" ('choose_a_doc', '[ Documents ]'),
MSG_numberOfSearchColumns = "Number of search columns" ('min_ref_violated', 'You must choose more elements here.'),
MSG_searchFields = "Search fields" ('max_ref_violated', 'Too much elements are selected here.'),
POD_ASKACTION = 'Trigger related action' ('no_ref', 'No object.'),
REF_NO = 'No object.' ('add_ref', 'Add a new one'),
REF_ADD = 'Add a new one' ('action_ok', 'The action has been successfully executed.'),
REF_MOVE_UP = 'Move up' ('action_ko', 'A problem occurred while executing the action.'),
REF_MOVE_DOWN = 'Move down' ('move_up', 'Move up'),
REF_INVALID_INDEX = 'No move occurred: please specify a valid number.' ('move_down', 'Move down'),
QUERY_CREATE = 'create' ('query_create', 'create'),
QUERY_IMPORT = 'import' ('query_import', 'import'),
QUERY_CONSULT_ALL = 'consult all' ('query_no_result', 'Nothing to see for the moment.'),
QUERY_NO_RESULT = 'Nothing to see for the moment.' ('query_consult_all', 'consult all'),
IMPORT_TITLE = 'Importing data into your application' ('import_title', 'Importing data into your application'),
IMPORT_SHOW_HIDE = 'Show / hide alreadly imported elements.' ('import_show_hide', 'Show / hide alreadly imported elements.'),
IMPORT_ALREADY = 'Already imported.' ('import_already', 'Already imported.'),
IMPORT_MANY = 'Import selected elements' ('import_many', 'Import selected elements'),
IMPORT_DONE = 'Import terminated successfully.' ('import_done', 'Import terminated successfully.'),
SEARCH_TITLE = 'Advanced search' ('search_title', 'Advanced search'),
SEARCH_BUTTON = 'Search' ('search_button', 'Search'),
SEARCH_OBJECTS = 'Search objects of this type.' ('search_objects', 'Search objects of this type.'),
SEARCH_RESULTS = 'Search results' ('search_results', 'Search results'),
SEARCH_NEW = 'New search' ('search_results_descr', ' '),
SEARCH_FROM = 'From' ('search_new', 'New search'),
SEARCH_TO = 'to' ('search_from', 'From'),
SEARCH_OR = 'or' ('search_to', 'to'),
SEARCH_AND = 'and' ('search_or', 'or'),
WORKFLOW_COMMENT = 'Optional comment' ('search_and', 'and'),
WORKFLOW_STATE = 'state' ('ref_invalid_index', 'No move occurred: please specify a valid number.'),
APPY_TITLE = 'Title' ('bad_long', 'An integer value is expected; do not enter any space.'),
DATA_CHANGE = 'Data change' ('bad_float', 'A floating-point number is expected; use the dot as decimal ' \
MODIFIED_FIELD = 'Modified field' 'separator, not a comma; do not enter any space.'),
PREVIOUS_VALUE = 'Previous value' ('bad_date', 'Please specify a valid date.'),
PHASE = 'phase' ('bad_email', 'Please enter a valid email.'),
CHOOSE_A_VALUE = ' - ' ('bad_url', 'Please enter a valid URL.'),
CHOOSE_A_DOC = '[ Documents ]' ('bad_alphanumeric', 'Please enter a valid alphanumeric value.'),
MIN_REF_VIOLATED = 'You must choose more elements here.' ('bad_select_value', 'The value is not among possible values for this field.'),
MAX_REF_VIOLATED = 'Too much elements are selected here.' ('select_delesect', '(Un)select all'),
BAD_LONG = 'An integer value is expected; do not enter any space.' ('no_elem_selected', 'You must select at least one element.'),
BAD_FLOAT = 'A floating-point number is expected; use the dot as decimal ' \ ('object_edit', 'Edit'),
'separator, not a comma; do not enter any space.' ('object_delete', 'Delete'),
BAD_DATE = 'Please specify a valid date.' ('object_unlink', 'Unlink'),
BAD_EMAIL = 'Please enter a valid email.' ('delete_confirm', 'Are you sure you want to delete this element?'),
BAD_URL = 'Please enter a valid URL.' ('unlink_confirm', 'Are you sure you want to unlink this element?'),
BAD_ALPHANUMERIC = 'Please enter a valid alphanumeric value.' ('delete_done', 'The element has been deleted.'),
BAD_SELECT_VALUE = 'The value is not among possible values for this field.' ('unlink_done', 'The element has been unlinked.'),
ACTION_OK = 'The action has been successfully executed.' ('goto_first', 'Go to top'),
ACTION_KO = 'A problem occurred while executing the action.' ('goto_previous', 'Go to previous'),
FRONT_PAGE_TEXT = 'Welcome to this Appy-powered site.' ('goto_next', 'Go to next'),
EMAIL_SUBJECT = '${siteTitle} - Action \\"${transitionName}\\" has been ' \ ('goto_last', 'Go to end'),
'performed on element entitled \\"${objectTitle}\\".' ('goto_source', 'Go back'),
EMAIL_BODY = 'You can consult this element at ${objectUrl}.' ('whatever', 'Whatever'),
SELECT_DESELECT = '(Un)select all' ('yes', 'Yes'),
NO_SELECTION = 'You must select at least one element.' ('no', 'No'),
EDIT = 'Edit' ('field_required', 'Please fill this field.'),
DELETE = 'Delete' ('field_invalid', 'Please fill or correct this.'),
UNLINK = 'Unlink' ('file_required', 'Please select a file.'),
DELETE_CONFIRM = 'Are you sure you want to delete this element?' ('image_required', 'The uploaded file must be an image.'),
UNLINK_CONFIRM = 'Are you sure you want to unlink this element?' ('odt', 'ODT'),
DELETE_DONE = 'The element has been deleted.' ('pdf', 'PDF'),
UNLINK_DONE = 'The element has been unlinked.' ('doc', 'DOC'),
GOTO_FIRST = 'Go to top' ('rtf', 'RTF'),
GOTO_PREVIOUS = 'Go to previous' ('front_page_text', 'Welcome to this Appy-powered site.'),
GOTO_NEXT = 'Go to next' ('captcha_text', 'Please type "${text}" (without the double quotes) in the ' \
GOTO_LAST = 'Go to end'
GOTO_SOURCE = 'Go back'
WHATEVER = 'Whatever'
CONFIRM = 'Are you sure ?'
YES = 'Yes'
NO = 'No'
FIELD_REQUIRED = 'Please fill this field.'
FILE_REQUIRED = 'Please select a file.'
FIELD_INVALID = 'Please fill or correct this.'
IMAGE_REQUIRED = 'The uploaded file must be an image.'
FORMAT_ODT = 'ODT'
FORMAT_PDF = 'PDF'
FORMAT_DOC = 'DOC'
FORMAT_RTF = 'RTF'
CAPTCHA_TEXT = 'Please type "${text}" (without the double quotes) in the ' \
'field besides, but without the character at position ' \ 'field besides, but without the character at position ' \
'${number}.' '${number}.'),
BAD_CAPTCHA = 'The code was not correct. Please try again.' ('bad_captcha', 'The code was not correct. Please try again.'),
LOGIN = 'Login' ('app_login', 'Login'),
CONNECT = 'Log in' ('app_connect', 'Log in'),
PASSWORD = 'Password' ('app_logout', 'Logout'),
LOGOUT = 'Logout' ('app_password', 'Password'),
HOME = 'Home' ('app_home', 'Home'),
LOGIN_RESERVED = 'This login is reserved.' ('login_reserved', 'This login is reserved.'),
LOGIN_IN_USE = 'This login is already in use.' ('login_in_use', 'This login is already in use.'),
LOGIN_OK = 'Welcome! You are now logged in.' ('login_ko', 'Welcome! You are now logged in.'),
LOGIN_KO = 'Login failed.' ('login_ok', 'Login failed.'),
PASSWORD_TOO_SHORT = 'Passwords must contain at least ${nb} characters.' ('password_too_short', 'Passwords must contain at least ${nb} characters.'),
PASSWORDS_MISMATCH = 'Passwords do not match.' ('passwords_mismatch', 'Passwords do not match.'),
SAVE = 'Save' ('object_save', 'Save'),
SAVED = 'Changes saved.' ('object_saved', 'Changes saved.'),
ERROR = 'Please correct the indicated errors.' ('validation_error', 'Please correct the indicated errors.'),
CANCEL = 'Cancel' ('object_cancel', 'Cancel'),
CANCELED = 'Changes canceled.' ('object_canceled', 'Changes canceled.'),
ENABLE_COOKIES = 'You must enable cookies before you can log in.' ('enable_cookies', 'You must enable cookies before you can log in.'),
PAGE_PREVIOUS = 'Previous page' ('page_previous', 'Previous page'),
PAGE_NEXT = 'Next page' ('page_next', 'Next page'),
FORGOT_PASSWORD = 'Forgot password?' ('forgot_password', 'Forgot password?'),
ASK_PASSWORD_REINIT = 'Ask new password' ('ask_password_reinit', 'Ask new password'),
WRONG_PASSWORD_REINIT = 'Something went wrong. First possibility: you ' \ ('wrong_password_reinit', 'Something went wrong. First possibility: you ' \
'have already clicked on the link (maybe have you double-clicked?) ' \ 'have already clicked on the link (maybe have you double-clicked?) ' \
'and your password has already been re-initialized. Please check ' \ 'and your password has already been re-initialized. Please check ' \
'that you haven\'t received your new password in another email. ' \ 'that you haven\'t received your new password in another email. ' \
@ -143,75 +128,97 @@ class PoMessage:
'splitted on several lines. In this case, please re-type the link in ' \ 'splitted on several lines. In this case, please re-type the link in ' \
'one single line and retry. Third possibility: you have waited too ' \ 'one single line and retry. Third possibility: you have waited too ' \
'long and your request has expired, or a technical error occurred. ' \ 'long and your request has expired, or a technical error occurred. ' \
'In this case, please try again to ask a new password from the start.' 'In this case, please try again to ask a new password from the start.'),
REINIT_MAIL_SENT = 'A mail has been sent to you. Please follow the ' \ ('reinit_mail_sent', 'A mail has been sent to you. Please follow the ' \
'instructions from this email.' 'instructions from this email.'),
REINIT_PASSWORD = 'Password re-initialisation' ('reinit_password', 'Password re-initialisation'),
REINIT_PASSWORD_BODY = 'Hello,<br/><br/>A password re-initialisation ' \ ('reinit_password_body', 'Hello,<br/><br/>A password re-initialisation ' \
'has been requested, tied to this email address, for the website ' \ 'has been requested, tied to this email address, for the website ' \
'${siteUrl}. If you are not at the origin of this request, please ' \ '${siteUrl}. If you are not at the origin of this request, please ' \
'ignore this email. Else, click on the link below to receive a new ' \ 'ignore this email. Else, click on the link below to receive a new ' \
'password.<br/><br/>${url}' 'password.<br/><br/>${url}'),
NEW_PASSWORD = 'Your new password' ('new_password', 'Your new password'),
NEW_PASSWORD_BODY = 'Hello,<br/><br/>The new password you have ' \ ('new_password_body', 'Hello,<br/><br/>The new password you have ' \
'requested for website ${siteUrl} is ${password}<br/>' \ 'requested for website ${siteUrl} is ${password}<br/>' \
'<br/>Best regards.' '<br/>Best regards.'),
NEW_PASSWORD_SENT = 'Your new password has been sent to you by email.' ('new_password_sent', 'Your new password has been sent to you by email.'),
LAST_USER_ACCESS = 'Last access' ('last_user_access', 'Last access'),
OBJECT_HISTORY = 'History' ('object_history', 'History'),
OBJECT_CREATED_BY = 'By' ('object_created_by', 'By'),
OBJECT_CREATED_ON = 'On' ('object_created_on', 'On'),
OBJECT_MODIFIED_ON = 'Last updated on' ('object_modified_on', 'Last updated on'),
OBJECT_ACTION = 'Action' ('object_action', 'Action'),
OBJECT_AUTHOR = 'Author' ('object_author', 'Author'),
ACTION_DATE = 'Date' ('action_date', 'Date'),
ACTION_COMMENT = 'Comment' ('action_comment', 'Comment'),
DAY_MON_SHORT = 'Mon' ('day_Mon_short', 'Mon'),
DAY_TUE_SHORT = 'Tue' ('day_Tue_short', 'Tue'),
DAY_WED_SHORT = 'Wed' ('day_Wed_short', 'Wed'),
DAY_THU_SHORT = 'Thu' ('day_Thu_short', 'Thu'),
DAY_FRI_SHORT = 'Fri' ('day_Fri_short', 'Fri'),
DAY_SAT_SHORT = 'Sat' ('day_Sat_short', 'Sat'),
DAY_SUN_SHORT = 'Sun' ('day_Sun_short', 'Sun'),
DAY_MON = 'Monday' ('day_Mon', 'Monday'),
DAY_TUE = 'Tuesday' ('day_Tue', 'Tuesday'),
DAY_WED = 'Wednesday' ('day_Wed', 'Wednesday'),
DAY_THU = 'Thursday' ('day_Thu', 'Thursday'),
DAY_FRI = 'Friday' ('day_Fri', 'Friday'),
DAY_SAT = 'Saturday' ('day_Sat', 'Saturday'),
DAY_SUN = 'Sunday' ('day_Sun', 'Sunday'),
AMPM_AM = 'AM' ('ampm_am', 'AM'),
AMPM_PM = 'PM' ('ampm_pm', 'PM'),
MONTH_JAN_SHORT = 'Jan' ('month_Jan_short', 'Jan'),
MONTH_FEB_SHORT = 'Feb' ('month_Feb_short', 'Feb'),
MONTH_MAR_SHORT = 'Mar' ('month_Mar_short', 'Mar'),
MONTH_APR_SHORT = 'Apr' ('month_Apr_short', 'Apr'),
MONTH_MAY_SHORT = 'May' ('month_May_short', 'May'),
MONTH_JUN_SHORT = 'Jun' ('month_Jun_short', 'Jun'),
MONTH_JUL_SHORT = 'Jul' ('month_Jul_short', 'Jul'),
MONTH_AUG_SHORT = 'Aug' ('month_Aug_short', 'Aug'),
MONTH_SEP_SHORT = 'Sep' ('month_Sep_short', 'Sep'),
MONTH_OCT_SHORT = 'Oct' ('month_Oct_short', 'Oct'),
MONTH_NOV_SHORT = 'Nov' ('month_Nov_short', 'Nov'),
MONTH_DEC_SHORT = 'Dec' ('month_Dec_short', 'Dec'),
MONTH_JAN = 'January' ('month_Jan', 'January'),
MONTH_FEB = 'February' ('month_Feb', 'February'),
MONTH_MAR = 'March' ('month_Mar', 'March'),
MONTH_APR = 'April' ('month_Apr', 'April'),
MONTH_MAY = 'May' ('month_May', 'May'),
MONTH_JUN = 'June' ('month_Jun', 'June'),
MONTH_JUL = 'July' ('month_Jul', 'July'),
MONTH_AUG = 'Augustus' ('month_Aug', 'Augustus'),
MONTH_SEP = 'September' ('month_Sep', 'September'),
MONTH_OCT = 'October' ('month_Oct', 'October'),
MONTH_NOV = 'November' ('month_Nov', 'November'),
MONTH_DEC = 'December' ('month_Dec', 'December'),
TODAY = 'Today' ('today', 'Today'),
WHICH_EVENT = 'Which event type would you like to create?' ('which_event', 'Which event type would you like to create?'),
EVENT_SPAN = 'Extend the event on the following number of days (leave ' \ ('event_span', 'Extend the event on the following number of days (leave ' \
'blank to create an event on the current day only):' 'blank to create an event on the current day only):'),
DEL_NEXT_EVENTS = 'Also delete successive events of the same type.' ('del_next_events', 'Also delete successive events of the same type.'),
]
# Some default values for labels whose ids are not fixed (so they can't be
# included in the previous variable).
CONFIG = "Configuration panel for product '%s'"
# The following messages (starting with MSG_) correspond to tool
# attributes added for every gen-class (warning: the message IDs correspond
# to MSG_<attributePrefix>).
MSG_podTemplate = "POD template for field '%s'"
MSG_formats = "Output format(s) for field '%s'"
MSG_resultColumns = "Columns to display while showing query results"
MSG_enableAdvancedSearch = "Enable advanced search"
MSG_numberOfSearchColumns = "Number of search columns"
MSG_searchFields = "Search fields"
POD_ASKACTION = 'Trigger related action'
EMAIL_SUBJECT = '${siteTitle} - Action \\"${transitionName}\\" has been ' \
'performed on element entitled \\"${objectTitle}\\".'
EMAIL_BODY = 'You can consult this element at ${objectUrl}.'
CONFIRM = 'Are you sure ?'
# ------------------------------------------------------------------------------
class PoMessage:
'''Represents a i18n message (po format).'''
def __init__(self, id, msg, default, fuzzy=False, comments=[], def __init__(self, id, msg, default, fuzzy=False, comments=[],
niceDefault=False): niceDefault=False):
self.id = id self.id = id
@ -290,22 +297,30 @@ class PoMessage:
return '<i18n msg id="%s", msg="%s", default="%s">' % \ return '<i18n msg id="%s", msg="%s", default="%s">' % \
(self.id, self.msg, self.default) (self.id, self.msg, self.default)
def __cmp__(self, other):
return cmp(self.id, other.id)
def clone(self, oldPrefix, newPrefix):
'''This returns a cloned version of myself. The clone has another id
that includes p_newPrefix.'''
if self.id.startswith(oldPrefix):
newId = newPrefix + self.id[len(oldPrefix):]
else:
newId = '%s_%s' % (newPrefix, self.id.split('_', 1)[1])
return PoMessage(newId, self.msg, self.default, comments=self.comments)
def getMessage(self): def getMessage(self):
'''Returns self.msg, but with some replacements.''' '''Returns self.msg, but with some replacements.'''
return self.msg.replace('<br/>', '\n').replace('\\"', '"') return self.msg.replace('<br/>', '\n').replace('\\"', '"')
class PoMessages:
'''A list of po messages under construction.'''
def __init__(self):
# The list of messages
self.messages = []
# A dict of message ids, useful for efficiently checking if an id is
# already in the list or not.
self.ids = {}
def append(self, id, default, nice=True):
'''Creates a new PoMessage and adds it to self.messages. If p_nice is
True, it produces a nice default value for the message.'''
# Avoir creating duplicate ids
if id in self.ids: return
message = PoMessage(id, '', default, niceDefault=nice)
self.messages.append(message)
self.ids[id] = True
def get(self): return self.messages
class PoHeader: class PoHeader:
def __init__(self, name, value): def __init__(self, name, value):
self.name = name self.name = name