Skip to content

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:

  1. English message - for standalone use and backward compatibility
  2. Error code - a unique identifier for each error type
  3. 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:

[python: **.py]
keywords = _:1 gettext:1 ngettext:1,2 N_:1

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

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

pybabel compile -d 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:

  1. Extract messages: pybabel extract -F babel.cfg -k N_ -o messages.pot . path/to/sortition_algorithms/
  2. Check the .pot file contains sortition error messages and report messages
  3. Initialize test language: pybabel init -i messages.pot -d translations -l test
  4. Add test translations to verify they appear in your app
  5. Compile: pybabel compile -d translations
  6. Trigger errors and run selections in the sortition library and verify translated messages appear

Best Practices

  1. Always provide fallback: If a message code is missing or empty, display the English message from str(error) or element.line
  2. Preserve context: When translating ParseTable errors, include row/column information from the error object
  3. Keep templates synchronized: After upgrading the sortition library, re-extract messages to catch new error codes and report message codes
  4. Test all paths: Ensure your translation code handles all error types and report messages the library can generate
  5. 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:

  1. Check that your babel.cfg includes the N_ keyword
  2. Verify the extraction command includes the sortition library path
  3. Ensure error codes in your translation files match those in error_messages.py
  4. Report issues at: https://github.com/anthropics/sortition-algorithms/issues