or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

csrf.mdfields.mdforms.mdi18n.mdindex.mdvalidation.mdwidgets.md

i18n.mddocs/

0

# Internationalization

1

2

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.

3

4

## Capabilities

5

6

### Translation Functions

7

8

Core functions for managing translations and message catalogs.

9

10

```python { .api }

11

def get_translations(languages=None, getter=None) -> object:

12

"""

13

Get translation object for specified languages.

14

15

Parameters:

16

- languages: List of language codes (e.g., ['en_US', 'en'])

17

- getter: Function to retrieve translations (default: get_builtin_gnu_translations)

18

19

Returns:

20

Translation object with gettext/ngettext methods

21

"""

22

23

def get_builtin_gnu_translations(languages=None) -> object:

24

"""

25

Get built-in GNU gettext translations from WTForms message catalogs.

26

27

Parameters:

28

- languages: List of language codes to load

29

30

Returns:

31

GNUTranslations object or NullTranslations if no translations found

32

"""

33

34

def messages_path() -> str:

35

"""

36

Get path to WTForms built-in message catalog directory.

37

38

Returns:

39

str: Path to locale directory containing .mo files

40

"""

41

```

42

43

### Translation Classes

44

45

Translation wrapper classes for different translation backends.

46

47

```python { .api }

48

class DefaultTranslations:

49

"""

50

Wrapper for gettext translations with fallback support.

51

Provides gettext and ngettext methods for message translation.

52

53

Parameters:

54

- translations: GNUTranslations object or compatible translation backend

55

"""

56

def __init__(self, translations): ...

57

58

def gettext(self, string) -> str:

59

"""

60

Get translated string.

61

62

Parameters:

63

- string: String to translate

64

65

Returns:

66

str: Translated string or original if no translation found

67

"""

68

69

def ngettext(self, singular, plural, n) -> str:

70

"""

71

Get translated string with pluralization.

72

73

Parameters:

74

- singular: Singular form string

75

- plural: Plural form string

76

- n: Number for pluralization

77

78

Returns:

79

str: Translated string in appropriate plural form

80

"""

81

82

class DummyTranslations:

83

"""

84

No-op translation class that returns strings unmodified.

85

Used as default when no translations are configured.

86

"""

87

def __init__(self): ...

88

89

def gettext(self, string) -> str:

90

"""Return string unmodified."""

91

return string

92

93

def ngettext(self, singular, plural, n) -> str:

94

"""Return singular or plural form based on n."""

95

return singular if n == 1 else plural

96

```

97

98

## Internationalization Usage Examples

99

100

### Basic Translation Setup

101

102

```python

103

from wtforms import Form, StringField, validators

104

from wtforms.i18n import get_translations

105

106

class MultilingualForm(Form):

107

class Meta:

108

locales = ['es_ES', 'en_US'] # Spanish, English fallback

109

cache_translations = True

110

111

name = StringField('Name', [validators.DataRequired()])

112

email = StringField('Email', [validators.Email()])

113

114

# Form will use Spanish translations for validation messages

115

form = MultilingualForm(formdata=request.form)

116

if not form.validate():

117

for field, errors in form.errors.items():

118

for error in errors:

119

print(error) # Error messages in Spanish

120

```

121

122

### Custom Translation Backend

123

124

```python

125

from wtforms.i18n import DefaultTranslations

126

import gettext

127

128

class CustomForm(Form):

129

class Meta:

130

def get_translations(self, form):

131

# Custom translation loading

132

domain = 'my_app'

133

localedir = '/path/to/my/locales'

134

language = get_current_language() # Your language detection

135

136

try:

137

translations = gettext.translation(

138

domain, localedir, languages=[language]

139

)

140

return DefaultTranslations(translations)

141

except IOError:

142

# Fallback to built-in translations

143

from wtforms.i18n import get_builtin_gnu_translations

144

return get_builtin_gnu_translations([language])

145

146

username = StringField('Username', [validators.DataRequired()])

147

```

148

149

### Per-Request Language Selection

150

151

```python

152

class LocalizedForm(Form):

153

username = StringField('Username', [validators.DataRequired()])

154

email = StringField('Email', [validators.Email()])

155

156

def create_form(request):

157

# Detect language from request

158

accepted_languages = request.headers.get('Accept-Language', '')

159

user_language = detect_language(accepted_languages)

160

161

# Create form with specific language

162

form = LocalizedForm(

163

formdata=request.form,

164

meta={

165

'locales': [user_language, 'en'], # User language + English fallback

166

'cache_translations': False # Don't cache for per-request languages

167

}

168

)

169

return form

170

171

@app.route('/register', methods=['GET', 'POST'])

172

def register():

173

form = create_form(request)

174

if form.validate():

175

# Process registration

176

pass

177

return render_template('register.html', form=form)

178

```

179

180

### Built-in Language Support

181

182

WTForms includes built-in translations for common validation messages:

183

184

```python

185

# Available built-in languages (as of version 3.2.1)

186

SUPPORTED_LANGUAGES = [

187

'ar', # Arabic

188

'bg', # Bulgarian

189

'ca', # Catalan

190

'cs', # Czech

191

'cy', # Welsh

192

'da', # Danish

193

'de', # German

194

'el', # Greek

195

'en', # English

196

'es', # Spanish

197

'et', # Estonian

198

'fa', # Persian

199

'fi', # Finnish

200

'fr', # French

201

'he', # Hebrew

202

'hu', # Hungarian

203

'it', # Italian

204

'ja', # Japanese

205

'ko', # Korean

206

'nb', # Norwegian Bokmål

207

'nl', # Dutch

208

'pl', # Polish

209

'pt', # Portuguese

210

'ru', # Russian

211

'sk', # Slovak

212

'sv', # Swedish

213

'tr', # Turkish

214

'uk', # Ukrainian

215

'zh', # Chinese

216

]

217

218

class MultiLanguageForm(Form):

219

class Meta:

220

# Try user's preferred language, fallback to English

221

locales = ['de', 'en'] # German with English fallback

222

223

email = StringField('E-Mail', [validators.Email()])

224

password = StringField('Passwort', [validators.DataRequired()])

225

226

# Validation messages will be in German

227

form = MultiLanguageForm(formdata={'email': 'invalid'})

228

form.validate()

229

print(form.email.errors) # ['Ungültige E-Mail-Adresse.']

230

```

231

232

### Custom Validation Messages

233

234

```python

235

class CustomMessageForm(Form):

236

username = StringField('Username', [

237

validators.DataRequired(message='El nombre de usuario es obligatorio'),

238

validators.Length(min=3, message='Mínimo 3 caracteres')

239

])

240

241

email = StringField('Email', [

242

validators.Email(message='Formato de email inválido')

243

])

244

245

def validate_username(self, field):

246

if User.exists(field.data):

247

raise ValidationError('Este nombre de usuario ya existe')

248

249

# Custom messages override translation system

250

form = CustomMessageForm(formdata={'username': 'ab'})

251

form.validate()

252

print(form.username.errors) # ['Mínimo 3 caracteres']

253

```

254

255

### Dynamic Message Translation

256

257

```python

258

from wtforms.validators import ValidationError

259

260

class TranslatedValidator:

261

"""Custom validator with translatable messages."""

262

263

def __init__(self, message_key='custom_validation_error'):

264

self.message_key = message_key

265

266

def __call__(self, form, field):

267

if not self.validate_logic(field.data):

268

# Get translated message using form's translation system

269

message = field.gettext(self.message_key)

270

raise ValidationError(message)

271

272

def validate_logic(self, value):

273

# Your validation logic here

274

return len(value) > 5

275

276

class TranslatedForm(Form):

277

class Meta:

278

locales = ['es', 'en']

279

280

data = StringField('Data', [TranslatedValidator()])

281

282

# Validation messages will be translated

283

form = TranslatedForm(formdata={'data': 'short'})

284

```

285

286

### Template Integration

287

288

```html

289

<!-- Jinja2 template with translated labels -->

290

<form method="POST">

291

{{ form.csrf_token }}

292

293

<div class="form-group">

294

{{ form.username.label(class="form-label") }}

295

{{ form.username(class="form-control") }}

296

{% for error in form.username.errors %}

297

<div class="error">{{ error }}</div>

298

{% endfor %}

299

</div>

300

301

<div class="form-group">

302

{{ form.email.label(class="form-label") }}

303

{{ form.email(class="form-control") }}

304

{% for error in form.email.errors %}

305

<div class="error">{{ error }}</div>

306

{% endfor %}

307

</div>

308

309

<button type="submit">{{ _('Submit') }}</button>

310

</form>

311

```

312

313

### Creating Custom Translation Files

314

315

```bash

316

# Create message catalog for your application

317

# 1. Extract messages from Python files

318

pybabel extract -F babel.cfg -k gettext -k ngettext -o messages.pot .

319

320

# 2. Initialize Spanish translation

321

pybabel init -i messages.pot -d translations -l es

322

323

# 3. Update existing translations

324

pybabel update -i messages.pot -d translations

325

326

# 4. Compile translations

327

pybabel compile -d translations

328

```

329

330

Example `babel.cfg`:

331

```ini

332

[python: **.py]

333

[jinja2: **/templates/**.html]

334

extensions=jinja2.ext.autoescape,jinja2.ext.with_

335

```

336

337

### Translation with Context Managers

338

339

```python

340

from contextlib import contextmanager

341

from flask_babel import get_locale

342

343

@contextmanager

344

def form_language(language_code):

345

"""Context manager for temporary language switching."""

346

original_locale = get_locale()

347

try:

348

# Set temporary locale

349

set_locale(language_code)

350

yield

351

finally:

352

# Restore original locale

353

set_locale(original_locale)

354

355

class FlexibleForm(Form):

356

message = StringField('Message', [validators.DataRequired()])

357

358

# Use form with specific language

359

with form_language('fr'):

360

form = FlexibleForm(formdata=request.form)

361

if not form.validate():

362

# Error messages in French

363

errors = form.errors

364

```

365

366

### Advanced Translation Configuration

367

368

```python

369

import os

370

import gettext

371

from wtforms.i18n import DefaultTranslations, DummyTranslations

372

373

class AdvancedI18nForm(Form):

374

class Meta:

375

@staticmethod

376

def get_translations(form):

377

"""Advanced translation configuration."""

378

379

# Get language from various sources

380

language = (

381

getattr(form, '_language', None) or # Form-specific

382

os.environ.get('FORM_LANGUAGE') or # Environment

383

request.headers.get('Accept-Language', '')[:2] or # Browser

384

'en' # Default

385

)

386

387

# Try multiple translation sources

388

translation_sources = [

389

('myapp', '/opt/myapp/translations'), # App-specific

390

('wtforms', get_wtforms_locale_path()), # WTForms built-in

391

]

392

393

for domain, locale_dir in translation_sources:

394

try:

395

trans = gettext.translation(

396

domain, locale_dir,

397

languages=[language, 'en'],

398

fallback=True

399

)

400

return DefaultTranslations(trans)

401

except IOError:

402

continue

403

404

# Final fallback

405

return DummyTranslations()

406

407

name = StringField('Name', [validators.DataRequired()])

408

409

# Set language per form instance

410

form = AdvancedI18nForm(formdata=request.form)

411

form._language = 'de' # German

412

```

413

414

### Pluralization Support

415

416

```python

417

from wtforms.validators import ValidationError

418

419

class ItemCountValidator:

420

"""Validator demonstrating pluralization."""

421

422

def __init__(self, min_items=1):

423

self.min_items = min_items

424

425

def __call__(self, form, field):

426

items = field.data or []

427

count = len(items)

428

429

if count < self.min_items:

430

# Use ngettext for proper pluralization

431

message = field.ngettext(

432

'You must select at least %(num)d item.',

433

'You must select at least %(num)d items.',

434

self.min_items

435

) % {'num': self.min_items}

436

raise ValidationError(message)

437

438

class ItemSelectionForm(Form):

439

class Meta:

440

locales = ['en', 'de', 'fr']

441

442

items = SelectMultipleField('Select Items', [

443

ItemCountValidator(min_items=2)

444

])

445

446

# Error messages will be properly pluralized in selected language

447

form = ItemSelectionForm(formdata={'items': ['one']})

448

form.validate()

449

# English: "You must select at least 2 items."

450

# German: "Sie müssen mindestens 2 Elemente auswählen."

451

```

452

453

### Framework Integration Examples

454

455

#### Flask-Babel Integration

456

457

```python

458

from flask import Flask

459

from flask_babel import Babel, get_locale

460

from wtforms.i18n import get_translations

461

462

app = Flask(__name__)

463

babel = Babel(app)

464

465

class FlaskBabelForm(Form):

466

class Meta:

467

def get_translations(self, form):

468

return get_translations([str(get_locale())])

469

470

message = StringField('Message', [validators.DataRequired()])

471

472

@babel.localeselector

473

def get_locale():

474

return request.args.get('lang') or 'en'

475

```

476

477

#### Django Integration

478

479

```python

480

from django.utils.translation import get_language

481

from django.utils.translation import gettext_lazy as _

482

483

class DjangoI18nForm(Form):

484

class Meta:

485

def get_translations(self, form):

486

from wtforms.i18n import get_translations

487

return get_translations([get_language()])

488

489

# Use Django's lazy translation for labels

490

message = StringField(_('Message'), [validators.DataRequired()])

491

```

492

493

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