Ticket #70: forms-nevow-i18n.diff
| File forms-nevow-i18n.diff, 19.2 kB (added by Mathieu, 3 years ago) |
|---|
-
forms/converters.py
old new 6 6 from nevow.compy import Adapter 7 7 from forms import iforms, validation 8 8 from zope.interface import implements 9 from forms.i18n import i_ 9 10 10 11 11 class NullConverter(Adapter): 12 12 implements( iforms.IStringConvertible ) 13 13 … … 39 39 try: 40 40 value = self.cast(value) 41 41 except ValueError: 42 raise validation.FieldValidationError( "Not a valid number")42 raise validation.FieldValidationError(i_("Not a valid number")) 43 43 return value 44 44 45 45 … … 67 67 if not value: 68 68 return None 69 69 if value not in ('True', 'False'): 70 raise validation.FieldValidationError( '%r should be either True or False')70 raise validation.FieldValidationError(i_("%r should be either True or False")%value) 71 71 return value == 'True' 72 72 73 73 … … 90 90 try: 91 91 y, m, d = [int(p) for p in value.split('-')] 92 92 except ValueError: 93 raise validation.FieldValidationError( 'Invalid date')93 raise validation.FieldValidationError(i_("Invalid date")) 94 94 try: 95 95 value = date(y, m, d) 96 96 except ValueError, e: 97 raise validation.FieldValidationError( 'Invalid date: '+str(e))97 raise validation.FieldValidationError(i_("Invalid date")+': '+str(e)) 98 98 return value 99 99 100 100 … … 131 131 h, m, s = parts 132 132 h, m, s, ms = int(h), int(m), int(s), int(ms) 133 133 except: 134 raise validation.FieldValidationError( 'Invalid time')134 raise validation.FieldValidationError(i_("Invalid time")) 135 135 136 136 try: 137 137 value = time(h, m, s, ms) 138 138 except ValueError, e: 139 raise validation.FieldValidationError( 'Invalid time: '+str(e))139 raise validation.FieldValidationError(i_("Invalid time")+': '+str(e)) 140 140 141 141 return value 142 142 … … 155 155 try: 156 156 value = date(*value) 157 157 except (TypeError, ValueError), e: 158 raise validation.FieldValidationError( 'Invalid date: '+str(e))158 raise validation.FieldValidationError(i_("Invalid date")+': '+str(e)) 159 159 return value 160 160 -
forms/validation.py
old new 1 1 import re 2 2 from zope.interface import implements 3 3 from forms import iforms 4 #for internationalisation 5 from forms.i18n import i_ 4 6 5 6 7 class FormsError(Exception): 7 8 """ 8 9 Base class for all Forms errors. A single string, message, is accepted and … … 54 55 55 56 def validate(self, field, value): 56 57 if value is None: 57 raise FieldRequiredError, 'Required'58 raise FieldRequiredError, i_("Required") 58 59 59 60 60 61 class LengthValidator(object): … … 69 70 70 71 def validationErrorMessage(self, field): 71 72 if self.min is not None and self.max is None: 72 return 'Must be longer than %r characters'%(self.min,)73 return i_("Must be longer than %r characters")%self.min 73 74 if self.min is None and self.max is not None: 74 return 'Must be shorter than %r characters'%(self.max,)75 return 'Must be between %r and %r characters'%(self.min, self.max)75 return i_("Must be shorter than %r characters")%self.max 76 return i_("Must be between %r and %r characters")%(self.min, self.max) 76 77 77 78 def validate(self, field, value): 78 79 if value is None: … … 96 97 97 98 def validationErrorMessage(self, field): 98 99 if self.min is not None and self.max is None: 99 return 'Must be greater than %r'%(self.min,)100 return i_("Must be greater than %r")%(self.min,) 100 101 if self.min is None and self.max is not None: 101 return 'Must be less than %r'%(self.max,)102 return 'Must be between %r and %r'%(self.min, self.max)102 return i_("Must be less than %r")%(self.max,) 103 return i_("Must be between %r and %r")%(self.min, self.max) 103 104 104 105 def validate(self, field, value): 105 106 if value is None: … … 128 129 if not hasattr(self.regex, 'match'): 129 130 self.regex = re.compile(self.regex) 130 131 if self.regex.match(value) is None: 131 raise FieldValidationError, 'Invalid format'132 raise FieldValidationError, i_("Invalid format") 132 133 133 134 134 135 __all__ = [ -
forms/iforms.py
old new 1 1 from nevow.compy import Interface 2 2 3 4 3 class IType(Interface): 5 4 def validate(self, value): 6 5 pass … … 9 8 class IStructure(Interface): 10 9 pass 11 10 11 #to remember the previous i18n config in the context 12 class ISavedI18NConfig(Interface): 13 pass 12 14 13 15 class IWidget(Interface): 14 16 -
forms/form.py
old new 9 9 from forms import iforms, util, validation 10 10 from resourcemanager import ResourceManager 11 11 from zope.interface import implements 12 #for internationalisation 13 from forms.i18n import i_, APP_NAME, I18N_DIR 14 from nevow.i18n import I18NConfig 12 15 13 14 16 SEPARATOR = '!!' 15 17 FORMS_KEY = '__nevow_form__' 16 18 WIDGET_RESOURCE_KEY = 'widget_resource' 17 19 18 20 19 21 def renderForm(name): 20 22 21 23 def _(ctx, data): 22 24 23 25 def _processForm( form, ctx, name ): 24 26 # Remember the form 25 27 ctx.remember(form, iforms.IForm) 28 26 29 27 # Create a keyed tag that will render the form when flattened. 30 # get the current i18n config from the context 31 conf = ctx.locate(inevow.II18NConfig) 32 33 # Create a keyed tag that will render the form when flattened. 28 34 tag = T.invisible(key=name)[inevow.IRenderer(form)] 29 35 30 36 # Create a new context, referencing the above tag, so that we don't … … 32 38 # rendering. 33 39 ctx = context.WovenContext(parent=ctx, tag=tag) 34 40 41 # put our config in the context as the saved one 42 # forms will use his own config and the saved one, depending one which message to translate 43 ctx.remember(conf, iforms.ISavedI18NConfig) 44 45 35 46 # Find errors for *this* form and remember things on the context 36 47 errors = iforms.IFormErrors(ctx, None) 37 48 if errors is not None and errors.formName == name: … … 80 91 81 92 def addField(self, name, type, widgetFactory=None, label=None, description=None, cssClass=None): 82 93 if not util.validIdentifier(name): 83 raise ValueError( '%r is an invalid field name'%name)94 raise ValueError(i_("%r is an invalid field name")%name) 84 95 type.name = name 85 96 if label is None: 86 97 label = util.titleFromName(name) … … 94 105 if self.actions is None: 95 106 self.actions = [] 96 107 if name in [action.name for action in self.actions]: 97 raise ValueError( 'Action with name %r already exists.' %name)108 raise ValueError(i_("Action with name %r already exists.")%name) 98 109 self.actions.append( Action(callback, name, validate, label) ) 99 110 100 111 def getField(self,fieldName): … … 127 138 128 139 # Get the request args 129 140 requestArgs = inevow.IRequest(ctx).args 141 130 142 131 143 # Decode the request arg names 132 144 charset = getPOSTCharset(ctx) … … 154 166 break 155 167 156 168 if callback is None: 157 raise Exception( 'The form has no callback and no action was found.')169 raise Exception(i_("The form has no callback and no action was found.")) 158 170 159 171 # Store an errors object in the context 160 172 errors = FormErrors(self.name) … … 232 244 return d 233 245 return appserver.NotFound 234 246 235 def renderHTTP(self, ctx): 247 def renderHTTP(self, ctx): 236 248 raise NotImplemented() 237 249 238 250 def _fileFromWidget(self, form, ctx, segments): … … 363 375 the same instance that is located when a form is rendered after validation 364 376 failure. 365 377 """ 378 366 379 # Get hold of the request 367 380 request = inevow.IRequest(ctx) 368 381 # Find or create the known forms instance … … 379 392 380 393 def cacheForm( form, name ): 381 394 if form is None: 382 raise Exception( 'Form %r not found'%name)395 raise Exception(i_("Form %r not found")%name) 383 396 form.name = name 384 397 # Make it a known 385 398 knownForms[name] = form … … 436 449 tag.fillSlots('name', self.original.name) 437 450 tag.fillSlots('id', util.keytocssid(ctx.key)) 438 451 tag.fillSlots('action', url.here) 452 #we put the forms' i18n config in the context in _renderErrors 439 453 tag.fillSlots('errors', self._renderErrors) 454 #restore the previous i18n config 455 conf = ctx.locate(iforms.ISavedI18NConfig) 456 ctx.remember(conf, inevow.II18NConfig) 457 440 458 tag.fillSlots('items', self._renderItems) 441 459 tag.fillSlots('hiddenitems', self._renderHiddenItems) 442 460 tag.fillSlots('actions', self._renderActions) 443 461 return tag 444 462 445 463 def _renderErrors(self, ctx, data): 464 446 465 errors = iforms.IFormErrors(ctx, None) 447 466 if errors is not None: 448 467 errors = errors.getFormErrors() 449 468 if not errors: 450 469 return '' 470 471 # put forms' i18n config in the context to translate the error messages 472 ctx.remember(I18NConfig(domain=APP_NAME, localeDir=I18N_DIR), inevow.II18NConfig) 451 473 452 474 errorList = T.ul() 453 475 for error in errors: … … 457 479 if isinstance(error, validation.FieldError): 458 480 name, type, label, description, cssClass = self.original.getField(error.fieldName) 459 481 errorList[ T.li[ T.strong[ label, ' : ' ], error.message ] ] 460 return T.div(class_='errors')[ T.p[ 'Please correct the following errors:'], errorList ]482 return T.div(class_='errors')[ T.p[i_("Please correct the following errors:")], errorList ] 461 483 462 484 def _renderItems(self, ctx, data): 463 485 if self.original.items is None: … … 482 504 def _renderItem(self, ctx, data): 483 505 484 506 def _(ctx, data): 485 507 508 # put forms' i18n config in the context to translate the messages 509 ctx.remember(I18NConfig(domain=APP_NAME, localeDir=I18N_DIR), inevow.II18NConfig) 510 486 511 name, type, label, description, cssClass = data 487 512 form = self.original 488 513 formErrors = iforms.IFormErrors(ctx, None) … … 523 548 else: 524 549 render = widget.render 525 550 ctx.tag.fillSlots('inputs', render(ctx, name, formData, formErrors)) 551 526 552 ctx.tag.fillSlots('message', message) 553 554 # put back the saved i18n config in the context so that the form's descriptions 555 # are translated 556 conf = ctx.locate(iforms.ISavedI18NConfig) 557 ctx.remember(conf, inevow.II18NConfig) 558 527 559 ctx.tag.fillSlots('description', T.div(class_='description')[description or '']) 560 528 561 562 # XXX / FIXME : 563 # there is still a problem here: 564 # at render time, forms has to translate both descriptions and error messages (those under the description, 565 # used as reminders) 566 # unfortunately, it would have to use both i18n configs (his own config and the saved one 567 # from the external web app) 568 # but it can't due to the actual code 569 # we prefer having the descriptions translated than the error messages (as they are already translated 570 # on top of the form) 571 # 572 # So the code structure should be changed (as in the _renderErrors method) to be able to use both i18n config 573 # at different times to translate all messages 574 529 575 return ctx.tag 530 576 531 577 return _ … … 553 599 if self.original.actions is None: 554 600 yield '' 555 601 return 556 602 603 #be sure to use the saved i18n config 604 conf = iforms.ISavedI18NConfig(ctx) 605 ctx.remember(conf, inevow.II18NConfig) 606 557 607 for action in self.original.actions: 558 608 yield T.invisible(data=action, render=self._renderAction) 559 609 -
forms/types.py
old new 5 5 from forms import iforms, validation 6 6 from zope.interface import implements 7 7 8 9 8 class Type(object): 10 9 11 10 implements( iforms.IType ) -
forms/util.py
old new 3 3 from nevow import inevow 4 4 from forms import iforms 5 5 6 7 6 _IDENTIFIER_REGEX = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$') 8 7 9 8 -
forms/i18n.py
old new 1 # -*- coding: iso-8859-1 -*- 2 """ 3 i18n for forms 4 """ 5 6 from nevow.i18n import Translator 7 import os 8 import pkg_resources 9 10 #the directory where is located forms' message dir (in absolute path) 11 I18N_DIR = os.path.abspath(pkg_resources.resource_filename('forms', 'messages')) 12 APP_NAME = 'forms' 13 14 i_ = Translator(gettextFunction='gettext') -
forms/resourcemanager.py
old new 5 5 from shutil import copyfileobj 6 6 from exceptions import IOError, OSError 7 7 8 9 8 class ResourceManagerException( Exception ): 10 9 def __init__( self, *args, **kwds ): 11 10 super( ResourceManagerException, self ).__init__( *args, **kwds ) -
forms/widget.py
old new 5 5 6 6 import itertools 7 7 from nevow import inevow, loaders, tags as T, util, url, static, rend 8 from nevow.i18n import _9 8 from forms import converters, iforms, validation 10 9 from forms.util import keytocssid 11 10 from forms.form import widgetResourceURL, widgetResourceURLFromContext 12 11 from zope.interface import implements 13 12 from twisted.internet import defer 13 from forms.i18n import i_ 14 14 15 16 15 # Marker object for args that are not supplied 17 16 _UNSET = object() 18 17 19 20 18 class TextInput(object): 21 19 """ 22 20 A text input field. … … 180 178 if len(pwds) == 0: 181 179 pwd = '' 182 180 elif len(pwds) == 1: 183 raise validation.FieldValidationError( 'Please enter the password twice for confirmation.')181 raise validation.FieldValidationError(i_("Please enter the password twice for confirmation.")) 184 182 else: 185 183 if pwds[0] != pwds[1]: 186 raise validation.FieldValidationError( 'Passwords do not match.')184 raise validation.FieldValidationError(i_("Passwords do not match.")) 187 185 return self.original.validate(pwds[0]) 188 186 189 187 … … 472 470 tag(class_='readonly', readonly='readonly') 473 471 474 472 if self.dayFirst: 475 return dayTag, ' / ', monthTag, ' / ', yearTag, ' ', _('(day/month/year)')473 return dayTag, ' / ', monthTag, ' / ', yearTag, ' ', i_('(day/month/year)') 476 474 else: 477 return monthTag, ' / ', dayTag, ' / ', yearTag, ' ', _('(month/day/year)')475 return monthTag, ' / ', dayTag, ' / ', yearTag, ' ', i_('(month/day/year)') 478 476 479 477 def render(self, ctx, key, args, errors): 480 478 converter = iforms.IDateTupleConvertible(self.original) … … 504 502 if not ymd: 505 503 ymd = None 506 504 elif len(ymd) != 3: 507 raise validation.FieldValidationError( "Invalid date")505 raise validation.FieldValidationError(i_("Invalid date")) 508 506 # So, we have what looks like a good attempt to enter a date. 509 507 if ymd is not None: 510 508 # If a 2-char year is allowed then prepend the century. … … 520 518 # By now, we should have a year of at least 4 characters. 521 519 if len(ymd[0]) < 4: 522 520 if self.twoCharCutoffYear is not None: 523 msg = "Please enter a 2 or 4 digit year"521 msg = i_("Please enter a 2 or 4 digit year") 524 522 else: 525 msg = "Please enter a 4 digit year"523 msg = i_("Please enter a 4 digit year") 526 524 raise validation.FieldValidationError(msg) 527 525 # Map to integers 528 526 try: 529 527 ymd = [int(p) for p in ymd] 530 528 except ValueError, e: 531 raise validation.FieldValidationError( "Invalid date")529 raise validation.FieldValidationError(i_("Invalid date")) 532 530 ymd = iforms.IDateTupleConvertible(self.original).toType(ymd) 533 531 return self.original.validate(ymd) 534 532 … … 592 590 if not value: 593 591 value = None 594 592 elif len(value) != 2: 595 raise validation.FieldValidationError( "Invalid date")593 raise validation.FieldValidationError(i_("Invalid date")) 596 594 if value is not None: 597 595 try: 598 596 value = [int(p) for p in value] 599 597 except ValueError, e: 600 raise validation.FieldValidationError( "Invalid date")598 raise validation.FieldValidationError(i_("Invalid date")) 601 599 if value[1] < 0 or value[1] > 99: 602 raise validation.FieldValidationError( "Invalid year. Please enter a two-digit year.")600 raise validation.FieldValidationError(i_("Invalid year. Please enter a two-digit year.")) 603 601 if value[0] > self.cutoffYear: 604 602 value[0] = 1900 + value[0] 605 603 else: … … 725 723 else: 726 724 yield T.p[value] 727 725 else: 728 yield T.p[T.strong[ 'nothing uploaded']]726 yield T.p[T.strong[i_("nothing uploaded")]] 729 727 730 728 yield T.input(name=namer('value'),value=value,type='hidden') 731 729 tag=T.input(name=key, id=keytocssid(ctx.key),type='file') … … 856 854 yield T.p[T.img(src=tmpURL)] 857 855 else: 858 856 # No uploaded file, no original 859 yield T.p[T.strong[ 'Nothing uploaded']]857 yield T.p[T.strong[i_("Nothing uploaded")]] 860 858 861 859 yield T.input(name=key, id=keytocssid(ctx.key),type='file') 862 860 … … 889 887 yield T.p[T.img(src=tmpURL)] 890 888 else: 891 889 # No uploaded file, no original 892 yield T.p[T.strong[ 'Nothing uploaded']]890 yield T.p[T.strong[i_("Nothing uploaded")]] 893 891 894 892 if originalKey: 895 893 # key of the original that can be used to get a file later -
setup.py
old new 10 10 author_email='matt@pollenation.net', 11 11 packages=find_packages(), 12 12 package_data={ 13 'forms': ['forms.css', 'html/*', 'js/*'], 13 'forms': ['forms.css', 'html/*', 'js/*', 'messages/fr/*.po','messages/fr/*.sh','messages/fr/LC_MESSAGES/*'], 14 #'forms': ['forms.css', 'html/*', 'js/*'], 14 15 }, 15 16 zip_safe = True, 16 17 )
