CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-wtforms

Form validation and rendering for Python web development.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

i18n.mddocs/

Internationalization

Multi-language support with built-in message translations and customizable translation backends for form labels, validation messages, and error text. WTForms provides comprehensive internationalization (i18n) capabilities that integrate with standard gettext workflows.

Capabilities

Translation Functions

Core functions for managing translations and message catalogs.

def get_translations(languages=None, getter=None) -> object:
    """
    Get translation object for specified languages.
    
    Parameters:
    - languages: List of language codes (e.g., ['en_US', 'en'])
    - getter: Function to retrieve translations (default: get_builtin_gnu_translations)
    
    Returns:
    Translation object with gettext/ngettext methods
    """

def get_builtin_gnu_translations(languages=None) -> object:
    """
    Get built-in GNU gettext translations from WTForms message catalogs.
    
    Parameters:
    - languages: List of language codes to load
    
    Returns:
    GNUTranslations object or NullTranslations if no translations found
    """

def messages_path() -> str:
    """
    Get path to WTForms built-in message catalog directory.
    
    Returns:
    str: Path to locale directory containing .mo files
    """

Translation Classes

Translation wrapper classes for different translation backends.

class DefaultTranslations:
    """
    Wrapper for gettext translations with fallback support.
    Provides gettext and ngettext methods for message translation.
    
    Parameters:
    - translations: GNUTranslations object or compatible translation backend
    """
    def __init__(self, translations): ...
    
    def gettext(self, string) -> str:
        """
        Get translated string.
        
        Parameters:
        - string: String to translate
        
        Returns:
        str: Translated string or original if no translation found
        """
    
    def ngettext(self, singular, plural, n) -> str:
        """
        Get translated string with pluralization.
        
        Parameters:
        - singular: Singular form string
        - plural: Plural form string  
        - n: Number for pluralization
        
        Returns:
        str: Translated string in appropriate plural form
        """

class DummyTranslations:
    """
    No-op translation class that returns strings unmodified.
    Used as default when no translations are configured.
    """
    def __init__(self): ...
    
    def gettext(self, string) -> str:
        """Return string unmodified."""
        return string
    
    def ngettext(self, singular, plural, n) -> str:
        """Return singular or plural form based on n."""
        return singular if n == 1 else plural

Internationalization Usage Examples

Basic Translation Setup

from wtforms import Form, StringField, validators
from wtforms.i18n import get_translations

class MultilingualForm(Form):
    class Meta:
        locales = ['es_ES', 'en_US']  # Spanish, English fallback
        cache_translations = True
    
    name = StringField('Name', [validators.DataRequired()])
    email = StringField('Email', [validators.Email()])

# Form will use Spanish translations for validation messages
form = MultilingualForm(formdata=request.form)
if not form.validate():
    for field, errors in form.errors.items():
        for error in errors:
            print(error)  # Error messages in Spanish

Custom Translation Backend

from wtforms.i18n import DefaultTranslations
import gettext

class CustomForm(Form):
    class Meta:
        def get_translations(self, form):
            # Custom translation loading
            domain = 'my_app'
            localedir = '/path/to/my/locales'
            language = get_current_language()  # Your language detection
            
            try:
                translations = gettext.translation(
                    domain, localedir, languages=[language]
                )
                return DefaultTranslations(translations)
            except IOError:
                # Fallback to built-in translations
                from wtforms.i18n import get_builtin_gnu_translations
                return get_builtin_gnu_translations([language])
    
    username = StringField('Username', [validators.DataRequired()])

Per-Request Language Selection

class LocalizedForm(Form):
    username = StringField('Username', [validators.DataRequired()])
    email = StringField('Email', [validators.Email()])

def create_form(request):
    # Detect language from request
    accepted_languages = request.headers.get('Accept-Language', '')
    user_language = detect_language(accepted_languages)
    
    # Create form with specific language
    form = LocalizedForm(
        formdata=request.form,
        meta={
            'locales': [user_language, 'en'],  # User language + English fallback
            'cache_translations': False  # Don't cache for per-request languages
        }
    )
    return form

@app.route('/register', methods=['GET', 'POST'])
def register():
    form = create_form(request)
    if form.validate():
        # Process registration
        pass
    return render_template('register.html', form=form)

Built-in Language Support

WTForms includes built-in translations for common validation messages:

# Available built-in languages (as of version 3.2.1)
SUPPORTED_LANGUAGES = [
    'ar',     # Arabic
    'bg',     # Bulgarian  
    'ca',     # Catalan
    'cs',     # Czech
    'cy',     # Welsh
    'da',     # Danish
    'de',     # German
    'el',     # Greek
    'en',     # English
    'es',     # Spanish
    'et',     # Estonian
    'fa',     # Persian
    'fi',     # Finnish
    'fr',     # French
    'he',     # Hebrew
    'hu',     # Hungarian
    'it',     # Italian
    'ja',     # Japanese
    'ko',     # Korean
    'nb',     # Norwegian Bokmål
    'nl',     # Dutch
    'pl',     # Polish
    'pt',     # Portuguese
    'ru',     # Russian
    'sk',     # Slovak
    'sv',     # Swedish
    'tr',     # Turkish
    'uk',     # Ukrainian
    'zh',     # Chinese
]

class MultiLanguageForm(Form):
    class Meta:
        # Try user's preferred language, fallback to English
        locales = ['de', 'en']  # German with English fallback
    
    email = StringField('E-Mail', [validators.Email()])
    password = StringField('Passwort', [validators.DataRequired()])

# Validation messages will be in German
form = MultiLanguageForm(formdata={'email': 'invalid'})
form.validate()
print(form.email.errors)  # ['Ungültige E-Mail-Adresse.']

Custom Validation Messages

class CustomMessageForm(Form):
    username = StringField('Username', [
        validators.DataRequired(message='El nombre de usuario es obligatorio'),
        validators.Length(min=3, message='Mínimo 3 caracteres')
    ])
    
    email = StringField('Email', [
        validators.Email(message='Formato de email inválido')
    ])
    
    def validate_username(self, field):
        if User.exists(field.data):
            raise ValidationError('Este nombre de usuario ya existe')

# Custom messages override translation system
form = CustomMessageForm(formdata={'username': 'ab'})
form.validate()
print(form.username.errors)  # ['Mínimo 3 caracteres']

Dynamic Message Translation

from wtforms.validators import ValidationError

class TranslatedValidator:
    """Custom validator with translatable messages."""
    
    def __init__(self, message_key='custom_validation_error'):
        self.message_key = message_key
    
    def __call__(self, form, field):
        if not self.validate_logic(field.data):
            # Get translated message using form's translation system
            message = field.gettext(self.message_key)
            raise ValidationError(message)
    
    def validate_logic(self, value):
        # Your validation logic here
        return len(value) > 5

class TranslatedForm(Form):
    class Meta:
        locales = ['es', 'en']
    
    data = StringField('Data', [TranslatedValidator()])

# Validation messages will be translated
form = TranslatedForm(formdata={'data': 'short'})

Template Integration

<!-- Jinja2 template with translated labels -->
<form method="POST">
    {{ form.csrf_token }}
    
    <div class="form-group">
        {{ form.username.label(class="form-label") }}
        {{ form.username(class="form-control") }}
        {% for error in form.username.errors %}
            <div class="error">{{ error }}</div>
        {% endfor %}
    </div>
    
    <div class="form-group">
        {{ form.email.label(class="form-label") }}
        {{ form.email(class="form-control") }}
        {% for error in form.email.errors %}
            <div class="error">{{ error }}</div>
        {% endfor %}
    </div>
    
    <button type="submit">{{ _('Submit') }}</button>
</form>

Creating Custom Translation Files

# Create message catalog for your application
# 1. Extract messages from Python files
pybabel extract -F babel.cfg -k gettext -k ngettext -o messages.pot .

# 2. Initialize Spanish translation
pybabel init -i messages.pot -d translations -l es

# 3. Update existing translations
pybabel update -i messages.pot -d translations

# 4. Compile translations
pybabel compile -d translations

Example babel.cfg:

[python: **.py]
[jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_

Translation with Context Managers

from contextlib import contextmanager
from flask_babel import get_locale

@contextmanager
def form_language(language_code):
    """Context manager for temporary language switching."""
    original_locale = get_locale()
    try:
        # Set temporary locale
        set_locale(language_code)
        yield
    finally:
        # Restore original locale
        set_locale(original_locale)

class FlexibleForm(Form):
    message = StringField('Message', [validators.DataRequired()])

# Use form with specific language
with form_language('fr'):
    form = FlexibleForm(formdata=request.form)
    if not form.validate():
        # Error messages in French
        errors = form.errors

Advanced Translation Configuration

import os
import gettext
from wtforms.i18n import DefaultTranslations, DummyTranslations

class AdvancedI18nForm(Form):
    class Meta:
        @staticmethod
        def get_translations(form):
            """Advanced translation configuration."""
            
            # Get language from various sources
            language = (
                getattr(form, '_language', None) or           # Form-specific
                os.environ.get('FORM_LANGUAGE') or            # Environment
                request.headers.get('Accept-Language', '')[:2] or  # Browser
                'en'                                          # Default
            )
            
            # Try multiple translation sources
            translation_sources = [
                ('myapp', '/opt/myapp/translations'),     # App-specific
                ('wtforms', get_wtforms_locale_path()),   # WTForms built-in
            ]
            
            for domain, locale_dir in translation_sources:
                try:
                    trans = gettext.translation(
                        domain, locale_dir, 
                        languages=[language, 'en'],
                        fallback=True
                    )
                    return DefaultTranslations(trans)
                except IOError:
                    continue
            
            # Final fallback
            return DummyTranslations()
    
    name = StringField('Name', [validators.DataRequired()])

# Set language per form instance
form = AdvancedI18nForm(formdata=request.form)
form._language = 'de'  # German

Pluralization Support

from wtforms.validators import ValidationError

class ItemCountValidator:
    """Validator demonstrating pluralization."""
    
    def __init__(self, min_items=1):
        self.min_items = min_items
    
    def __call__(self, form, field):
        items = field.data or []
        count = len(items)
        
        if count < self.min_items:
            # Use ngettext for proper pluralization
            message = field.ngettext(
                'You must select at least %(num)d item.',
                'You must select at least %(num)d items.', 
                self.min_items
            ) % {'num': self.min_items}
            raise ValidationError(message)

class ItemSelectionForm(Form):
    class Meta:
        locales = ['en', 'de', 'fr']
    
    items = SelectMultipleField('Select Items', [
        ItemCountValidator(min_items=2)
    ])

# Error messages will be properly pluralized in selected language
form = ItemSelectionForm(formdata={'items': ['one']})
form.validate()
# English: "You must select at least 2 items."
# German: "Sie müssen mindestens 2 Elemente auswählen."

Framework Integration Examples

Flask-Babel Integration

from flask import Flask
from flask_babel import Babel, get_locale
from wtforms.i18n import get_translations

app = Flask(__name__)
babel = Babel(app)

class FlaskBabelForm(Form):
    class Meta:
        def get_translations(self, form):
            return get_translations([str(get_locale())])
    
    message = StringField('Message', [validators.DataRequired()])

@babel.localeselector
def get_locale():
    return request.args.get('lang') or 'en'

Django Integration

from django.utils.translation import get_language
from django.utils.translation import gettext_lazy as _

class DjangoI18nForm(Form):
    class Meta:
        def get_translations(self, form):
            from wtforms.i18n import get_translations
            return get_translations([get_language()])
    
    # Use Django's lazy translation for labels
    message = StringField(_('Message'), [validators.DataRequired()])

This comprehensive internationalization system allows WTForms to be used effectively in multilingual applications across different web frameworks and deployment scenarios.

Install with Tessl CLI

npx tessl i tessl/pypi-wtforms

docs

csrf.md

fields.md

forms.md

i18n.md

index.md

validation.md

widgets.md

tile.json