Internationalization (i18n) Guide¶
This library provides comprehensive support for translating error messages into different languages for use in multilingual web applications.
Overview¶
All error messages in the sortition algorithms library include:
- English message - for standalone use and backward compatibility
- Error code - a unique identifier for each error type
- Structured parameters - data needed to reconstruct the message in any language
This design allows web applications to translate all error messages while keeping the library independent of any specific translation framework.
For Web Application Developers¶
Basic Approach¶
This library provides translation support for both error messages and run report messages. You can access translation data in the following ways:
Simple Errors (BadDataError, SelectionError, ValueError, etc.)¶
All simple exceptions now include error_code and error_params attributes:
from sortition_algorithms.errors import BadDataError, SelectionError
try:
# ... sortition code
except (BadDataError, SelectionError, ValueError, TypeError, RuntimeError) as e:
if hasattr(e, 'error_code') and e.error_code:
# Translate using the error code and parameters
translated_msg = _(f"errors.{e.error_code}") % e.error_params
print(translated_msg)
else:
# Fallback to English message
print(str(e))
ParseTable Errors (Validation Errors)¶
For ParseTableMultiError exceptions, iterate through the structured error data:
from sortition_algorithms.errors import ParseTableMultiError
try:
# ... sortition code
except ParseTableMultiError as e:
for sub_error in e.all_errors:
# Access the error code and parameters
if sub_error.error_code:
# Translate the core message
translated_msg = _(f"errors.{sub_error.error_code}") % sub_error.error_params
# Add context (row/column information)
if hasattr(sub_error, 'keys'): # Multi-column error
context = _("errors.parse_error_multi_column") % {
'msg': translated_msg,
'row': sub_error.row,
'keys': ', '.join(sub_error.keys)
}
else: # Single-column error
context = _("errors.parse_error_single_column") % {
'msg': translated_msg,
'row': sub_error.row,
'key': sub_error.key
}
print(context)
else:
# Fallback to English message
print(str(sub_error))
Run Report Messages¶
The RunReport object returned by sortition operations contains informational messages about the process. Each message includes translation data:
from sortition_algorithms.core import run_stratification
success, selected_people, report = run_stratification(
features, people, number_people_wanted, settings
)
# Iterate through report messages and translate them
for element in report._data:
# Check if this is a message line (not a table or error)
if hasattr(element, 'message_code') and element.message_code:
# Translate using the message code and parameters
translated_msg = _(f"report.{element.message_code}") % element.message_params
print(translated_msg)
elif hasattr(element, 'line'):
# Fallback to English message
print(element.line)
Alternatively, you can use the serialization feature to extract all translation data:
serialized_report = report.serialize()
for element in serialized_report['_data']:
if 'message_code' in element and element['message_code']:
# Web app can translate based on message_code
translated_msg = _(f"report.{element['message_code']}") % element['message_params']
elif 'line' in element:
# For messages without translation codes, use the English text
fallback_msg = element['line']
Flask-Babel Integration¶
For Flask applications using Babel for i18n:
1. Configure Babel to Extract Messages¶
Create or update babel.cfg in your web application:
2. Extract Messages from Both App and Library¶
Run the extraction command including the sortition library:
pybabel extract -F babel.cfg \
-k N_ \
-o messages.pot \
your_web_app/ \
venv/lib/python3.x/site-packages/sortition_algorithms/
This will extract all marked strings from both your application and the sortition library into a single .pot file.
3. Initialize or Update Translations¶
# For a new language (e.g., French)
pybabel init -i messages.pot -d translations -l fr
# To update existing translations
pybabel update -i messages.pot -d translations
4. Translate the Messages¶
Edit the .po files in translations/<language>/LC_MESSAGES/messages.po:
# English
msgid "No '%(column)s' column %(error_label)s found in %(data_container)s!"
msgstr ""
# French translation
msgid "No '%(column)s' column %(error_label)s found in %(data_container)s!"
msgstr "Aucune colonne '%(column)s' %(error_label)s trouvée dans %(data_container)s!"
5. Compile Translations¶
Available Error Codes¶
The library provides error codes for all validation and runtime errors. Here are the main categories:
Data Validation Errors¶
| Error Code | Parameters | Description |
|---|---|---|
missing_column |
column, error_label, data_container | Required column not found |
duplicate_column |
column, error_label, data_container | Multiple columns with same name |
empty_feature_value |
value_column_name, feature_column_name, feature_name | Blank value in feature data |
value_not_in_feature |
value, feature_column_name, feature_name | Invalid feature value for person |
empty_value_in_feature |
feature_column_name, feature_name | Missing feature value for person |
Numeric Validation Errors¶
| Error Code | Parameters | Description |
|---|---|---|
no_value_set |
field | No value provided for required numeric field |
not_a_number |
value | Value cannot be parsed as a number |
min_greater_than_max |
min, max | Minimum exceeds maximum |
min_flex_greater_than_min |
min_flex, min | Flexible minimum exceeds minimum |
max_flex_less_than_max |
max_flex, max | Flexible maximum less than maximum |
System Errors¶
| Error Code | Parameters | Description |
|---|---|---|
spreadsheet_not_found |
spreadsheet_name | Google Spreadsheet not accessible |
tab_not_found |
tab_name, spreadsheet_title | Worksheet tab not found |
unknown_selection_algorithm |
algorithm | Invalid algorithm specified |
gurobi_not_available |
(none) | Gurobi solver not installed |
See sortition_algorithms/error_messages.py for the complete list of error codes and their message templates.
Report Message Codes¶
The library provides message codes for all run report messages. Here are the main categories:
| Category | Code | Parameters | Description |
|---|---|---|---|
| Data Loading | loading_features_from_string |
(none) | Loading features from string data |
loading_people_from_string |
(none) | Loading people from string data | |
loading_features_from_file |
file_path | Loading features from file | |
loading_people_from_file |
file_path | Loading people from file | |
features_found |
count | Number of features loaded | |
| Algorithm | using_legacy_algorithm |
(none) | Using legacy selection algorithm |
gurobi_unavailable_switching |
(none) | Gurobi unavailable, switching to maximin | |
distribution_stats |
total_committees, non_zero_committees | Distribution statistics | |
| Selection Process | test_selection_warning |
(none) | Warning about non-random test selection |
initial_state |
(none) | Initial state message | |
trial_number |
trial | Current trial number | |
selection_success |
(none) | Selection succeeded | |
selection_failed |
attempts | Selection failed after N attempts | |
| Validation | blank_id_skipped |
row | Blank ID cell found and skipped |
See sortition_algorithms/report_messages.py for the complete list of report message codes and their templates.
Message Reference¶
All translatable strings are defined in:
# Error messages
from sortition_algorithms.error_messages import ERROR_MESSAGES, N_
# Report messages
from sortition_algorithms.report_messages import REPORT_MESSAGES, get_message
Both ERROR_MESSAGES and REPORT_MESSAGES dictionaries map message codes to their English message templates using Python % string formatting.
The N_() function is a no-op marker that identifies strings for extraction by Babel/gettext tools without requiring any translation framework dependency.
The get_message() helper function creates formatted messages from message codes and parameters.
Example: Complete Flask Integration¶
from flask import Flask
from flask_babel import Babel, gettext as _
from sortition_algorithms.core import run_stratification
from sortition_algorithms.errors import ParseTableMultiError, SelectionError
app = Flask(__name__)
babel = Babel(app)
def translate_sortition_error(error):
"""Translate a sortition error into the current language."""
if isinstance(error, ParseTableMultiError):
translated_lines = []
for sub_error in error.all_errors:
if sub_error.error_code:
# Translate core message
msg_key = f"errors.{sub_error.error_code}"
core_msg = _(msg_key) % sub_error.error_params
# Add context
if hasattr(sub_error, 'keys'):
full_msg = _("errors.parse_error_multi_column") % {
'msg': core_msg,
'row': sub_error.row,
'keys': ', '.join(sub_error.keys)
}
else:
full_msg = _("errors.parse_error_single_column") % {
'msg': core_msg,
'row': sub_error.row,
'key': sub_error.key
}
translated_lines.append(full_msg)
else:
# Fallback to English
translated_lines.append(str(sub_error))
return '\n'.join(translated_lines)
else:
# For other errors, use English for now
return str(error)
def translate_run_report(report):
"""Translate run report messages into the current language."""
translated_lines = []
for element in report._data:
if hasattr(element, 'message_code') and element.message_code:
# Translate the message
msg_key = f"report.{element.message_code}"
translated_msg = _(msg_key) % element.message_params
translated_lines.append(translated_msg)
elif hasattr(element, 'line'):
# Fallback to English for messages without translation codes
translated_lines.append(element.line)
# Note: tables and errors would need additional handling
return '\n'.join(translated_lines)
@app.route('/select')
def select_committee():
try:
success, selected, report = run_stratification(...)
translated_report = translate_run_report(report)
return {
"status": "success",
"selected": list(selected[0]) if selected else [],
"report": translated_report
}
except (ParseTableMultiError, SelectionError) as e:
translated_error = translate_sortition_error(e)
return {"status": "error", "message": translated_error}, 400
Testing Translations¶
To test your translations:
- Extract messages:
pybabel extract -F babel.cfg -k N_ -o messages.pot . path/to/sortition_algorithms/ - Check the
.potfile contains sortition error messages and report messages - Initialize test language:
pybabel init -i messages.pot -d translations -l test - Add test translations to verify they appear in your app
- Compile:
pybabel compile -d translations - Trigger errors and run selections in the sortition library and verify translated messages appear
Best Practices¶
- Always provide fallback: If a message code is missing or empty, display the English message from
str(error)orelement.line - Preserve context: When translating ParseTable errors, include row/column information from the error object
- Keep templates synchronized: After upgrading the sortition library, re-extract messages to catch new error codes and report message codes
- Test all paths: Ensure your translation code handles all error types and report messages the library can generate
- Handle all report elements: RunReport can contain lines, tables, and errors - ensure your translation code handles each type appropriately
Future Enhancements¶
Future versions may add:
- Helper functions for common translation patterns
- Pre-built integration examples for Django, FastAPI, etc.
- Translation utilities for generating .pot files directly from the library
Getting Help¶
If you encounter issues with i18n:
- Check that your
babel.cfgincludes theN_keyword - Verify the extraction command includes the sortition library path
- Ensure error codes in your translation files match those in
error_messages.py - Report issues at: https://github.com/anthropics/sortition-algorithms/issues