diff --git a/src/ocrmypdf/api.py b/src/ocrmypdf/api.py index b90973e9..605b8b1c 100644 --- a/src/ocrmypdf/api.py +++ b/src/ocrmypdf/api.py @@ -283,6 +283,34 @@ def _check_no_conflicting_ocr_params( ) +def _remap_language_to_languages(options_kwargs: dict) -> None: + """Map the public API 'language' parameter to OcrOptions 'languages' field. + + The public API uses 'language' (matching CLI --language) but OcrOptions + uses 'languages' (plural). This also coerces a bare string to a list + and splits '+'-separated language codes (e.g. 'eng+deu' -> ['eng', 'deu']) + to match the CLI behavior. + """ + if 'language' in options_kwargs and 'languages' not in options_kwargs: + lang = options_kwargs.pop('language') + if lang is None: + return + if isinstance(lang, str): + lang = lang.split('+') + else: + # Flatten any '+'-separated entries in the list + expanded: list[str] = [] + for item in lang: + if isinstance(item, str) and '+' in item: + expanded.extend(item.split('+')) + else: + expanded.append(item) + lang = expanded + options_kwargs['languages'] = lang + elif 'language' in options_kwargs: + del options_kwargs['language'] + + def create_options( *, input_file: PathOrIO, output_file: PathOrIO, parser: ArgumentParser, **kwargs ) -> OcrOptions: @@ -304,6 +332,9 @@ def create_options( # Prepare kwargs for direct OcrOptions construction options_kwargs = kwargs.copy() + # Map API parameter 'language' to OcrOptions field 'languages' + _remap_language_to_languages(options_kwargs) + # Set input and output files options_kwargs['input_file'] = input_file options_kwargs['output_file'] = output_file @@ -762,6 +793,9 @@ def _pdf_to_hocr( # noqa: D417 ): options_kwargs[param_name] = param_value + # Map API parameter 'language' to OcrOptions field 'languages' + _remap_language_to_languages(options_kwargs) + # Handle plugins if plugins: options_kwargs['plugins'] = plugins diff --git a/tests/test_api.py b/tests/test_api.py index dc32fdfb..03db01db 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -22,6 +22,64 @@ def test_language_list(): ocrmypdf.ocr('doesnotexist.pdf', '_.pdf', language=['eng', 'deu']) +def test_language_parameter_mapped_to_languages(): + """Test that the API 'language' parameter is mapped to OcrOptions 'languages'. + + Regression test for GitHub issue #1640: the Python API ignored the language + parameter, always defaulting to 'eng'. + """ + from ocrmypdf._options import OcrOptions + from ocrmypdf.api import create_options, setup_plugin_infrastructure + from ocrmypdf.cli import get_parser + + setup_plugin_infrastructure() + parser = get_parser() + + options = create_options( + input_file='test.pdf', + output_file='output.pdf', + parser=parser, + language=['tam'], + ) + assert options.languages == ['tam'] + + # Test with a list of multiple languages + options = create_options( + input_file='test.pdf', + output_file='output.pdf', + parser=parser, + language=['fra', 'deu'], + ) + assert options.languages == ['fra', 'deu'] + + # Test with a bare string (single language) + options = create_options( + input_file='test.pdf', + output_file='output.pdf', + parser=parser, + language='tam', + ) + assert options.languages == ['tam'] + + # Test '+'-separated string is split like CLI --language + options = create_options( + input_file='test.pdf', + output_file='output.pdf', + parser=parser, + language='eng+spa', + ) + assert options.languages == ['eng', 'spa'] + + # Test '+'-separated entry within a list is also split + options = create_options( + input_file='test.pdf', + output_file='output.pdf', + parser=parser, + language=['eng+spa'], + ) + assert options.languages == ['eng', 'spa'] + + def test_stream_api(resources: Path): in_ = (resources / 'graph.pdf').open('rb') out = BytesIO()