Skip to main content

legesher

Command-line interface for coding in any language

Status: Pre-Beta | Languages: 56 | Version: see pyproject.toml

The legesher CLI is your gateway to programming in your native language. Translate Python code between languages, manage git integration for collaborative workflows, and configure your multilingual development environment.

About

The legesher package provides command-line tools for developers who want to write code in their native language instead of English. It handles:

  • Project Setup: legesher init scaffolds configuration, git integration, and AI context
  • Code Translation: Convert Python files between languages using language packs
  • Execution: legesher run executes a native-language file directly
  • Git Integration: a clean filter that keeps each file in the language its author wrote it in
  • Configuration: Manage project and user settings for language preferences
  • Metadata: Inspect and validate the translation history recorded for your files

This is the main user-facing package in the Legesher ecosystem, built on top of legesher-core and legesher-i18n.

Installation

pip install legesher

Or install from source in the monorepo:

cd libs/legesher
pip install -e .

Quick Start

# Initialize a new project with Spanish language pack
legesher init --language es

# Translate a Python file to Spanish
legesher translate mi_codigo.py --to es

# Set up git integration for collaborative development
legesher git setup

# View available language packs
legesher-i18n registry list

Commands

legesher init

Scaffold Legesher in a Python project: write .legesher/config.yml, optionally wire the git filters, and optionally generate AI context files.

# Interactive setup
legesher init

# Pre-select languages and skip the prompts
legesher init --language es --language fr --git --no-ai-context

legesher translate

Translate Python source files between languages.

# Translate a single file to Spanish
legesher translate app.py --to es

# Translate to English, the run form Python executes. --from defaults to en, so name the source
legesher translate mi_app.py --from es --to en

# Between two non-English languages, go through English: direct pairs are not supported yet
legesher translate code.py --from es --to en --output code_en.py
legesher translate code_en.py --to fr --output code_fr.py

# Translate and write to different file
legesher translate input.py --to es --output salida.py

# Translate an entire directory
legesher translate src/ --to es --output traducido/

Full LLM Translation (translates identifiers, comments, strings, docstrings):

# Full translation with Anthropic Claude
legesher translate src/ --to es --full --llm-backend anthropic

# Full translation with local model (vLLM, Ollama, llama.cpp)
legesher translate src/ --to ur --full --llm-backend openai-compat \
    --llm-base-url http://localhost:8000

# Batch with cross-file consistency and resume
legesher translate src/ --to zh --full \
    --identifier-map .legesher/identifier_map.json \
    --checkpoint .legesher/checkpoint.txt

Options:

  • --to LANG: Target language code (required)
  • --from LANG: Source language code (default: en)
  • --output PATH: Output file or directory. Omitted, a single file goes to stdout and a directory goes to a sibling <dir>_translated/. Your input file is never overwritten in place
  • --full: Enable full LLM translation (identifiers, comments, strings, docstrings)
  • --llm-backend: LLM backend: anthropic, cohere-aya, or openai-compat (default: anthropic)
  • --llm-base-url: Base URL for openai-compat backend (e.g., http://localhost:8000)
  • --llm-model: Model name for any backend (default: the backend's own default)
  • --identifier-map PATH: Load/save identifier glossary for cross-file consistency (default: .legesher/identifier_map.json); --no-identifier-map disables it
  • --checkpoint PATH: Resume interrupted batch jobs

Example Output:

$ legesher translate hello.py --to es
definir greet():
    imprimir("hi")

$ legesher translate hello.py --to es --output hola.py
✓ Translated hello.py → hola.py

legesher run

Execute a native-language Python file.

legesher run programa.py --lang es

# Pass arguments through to the script
legesher run programa.py --lang es --args "--verbose input.txt"

--lang wins. Without it the language comes from the language: key in .legesher/config.yml, falling back to en when that key is absent. A config written by legesher init does not have that key (see Configuration), so dropping --lang there runs the file untranslated and Python raises SyntaxError. Pass --lang, or write the config with legesher git setup.

legesher git

Git stores each file in its author's language. legesher git setup configures the clean filter, as one long-running process per git command, and the guard hook for a repository.

# Set up for the current repository
legesher git setup

# Set up with a specific language
legesher git setup --language es

# Opt in to the keyword-only smudge (terminal editors)
legesher git setup --language es --keyword-smudge

# Check the configuration
legesher git status

Subcommands:

  • setup: Configure the clean filter, which only normalizes: a stray keyword, such as an English if typed into a Spanish file, is stored in its block's language. It never refuses a file, because git stores a file unchanged when its filter fails, so setup also installs a pre-commit hook that runs legesher guard. An existing pre-commit hook is never overwritten; setup prints the line to add to it instead. --no-hook skips the hook, and removes it if setup installed it.
  • status: Display the filter, smudge and hook configuration and the language settings

Smudge is off by default: a reader's rendering is never written into a file, so views render in the editor and on GitHub. --keyword-smudge is the opt-in for terminal editors. It renders an English file's keywords in your language on checkout, from the open language pack only (never builtins or library names), and only when the clean filter can store the result back exactly. Running setup again without the flag turns it off in that scope, and setup says so when a smudge is still configured in another, such as the global git config.

Until in-file region markers land, a file is one block. Its language is read from its committed version or, for a new file, from the file itself, among English and your configured language. A reading counts only when the file parses under it, and the language with the most keywords wins, so one slip never converts a file; with no reading that parses, or a tie, the file is stored as written. A file in a third language is left as written unless every keyword it uses is spelled the same in your language, which closely related languages can do.

One process per git command. Setup configures git's long-running filter process (filter.legesher.process), so a git command starts the filter once instead of once per file; the single-file clean command stays configured for git older than 2.11. The process runs through a small launcher, .git/legesher/filter-process (~/.legesher/filter-process with --global), which names the legesher-filter that ran setup and falls back to PATH. Git fails a whole command when a filter process ends before its handshake, so the launcher answers the handshake itself and only then starts the filter: a filter that is missing, broken or too old costs only that file's filtering, and git stores it as written, as it does for a missing single-file filter. A single-file smudge command in any git config is ignored once a process is configured; the process offers the smudge only when setup ran with --keyword-smudge. To take Legesher's filter out of a repository, run git config --remove-section filter.legesher.

Example Output:

$ legesher git setup --language es
✓ Found git repository at: /path/to/repo

Validating language pack 'es'...
✓ Loaded language pack: es (39 keywords, 143 builtins)

Creating configuration...
✓ Saved project config to: /path/to/repo/.legesher/config.yml

Configuring git filters...
✓ Filter process: sh '/path/to/repo/.git/legesher/filter-process'
✓ Clean filter (git before 2.11): legesher-filter --clean --path %f

Configuring .gitattributes...
✓ Updated .gitattributes

Initializing metadata system...
✓ Created metadata directory: /path/to/repo/.legesher/metadata
✓ Metadata will be tracked in git
✓ Metadata system initialized successfully

Configuring the guard hook...
✓ Installed the guard hook (pre-commit: legesher guard --staged)

Verifying setup...
✓ Git filter setup verified

============================================================
✓ Git filter setup complete!
============================================================

Your repository is now configured for multilingual code.
Language: es
Storage: each file in its author's language

The trailing "Next steps" list is omitted above. The 143 builtins in that output is not the pack's built-in count: the translator merges the pack's 70 exception names into its built-in map before reporting, so 143 is the pack's 73 built-ins plus its 70 exceptions. The per-category counts are in legesher-i18n.

legesher guard

Refuses what must not be committed. The pre-commit hook runs legesher guard --staged; without paths it checks the staged files, and with paths it checks those files on disk. --diff BASE checks only what changed from BASE to HEAD, read from HEAD and judged against each file's version at BASE: the command a pull-request check runs. It compares the two trees, so with a pull request's merge commit checked out it covers exactly the pull request, and a shallow clone only needs the base commit itself. --format github prints each finding as a GitHub Actions annotation. A file is checked if .gitattributes gives it the legesher filter (staged or in the working tree for --staged; at the base or the head for --diff); files under .legesher/ are always checked.

$ legesher guard --staged
src/app.py:2:11: unicode: U+202E RIGHT-TO-LEFT OVERRIDE: Trojan Source bidi reordering control (CVE-2021-42574)
src/app.py:3:5: keyword: `if` in a block stored in es; its stored form is `si`

legesher guard: 2 finding(s) in 1 file(s).
  • encoding: content that is not UTF-8, or a coding declaration other than UTF-8. Python honors the declaration, so a pure-ASCII file declared utf-7 can run code that reads as a string.
  • unicode: bidi reordering controls, invisible characters outside string and comment prose, Arabic tatweel, and identifier characters outside every supported script. There is no override.
  • keyword: a keyword not in its block's stored form. The clean filter normalizes these, so a finding means the file skipped it; run legesher git setup, then git add --renormalize <path>.
  • artifact: a symlink marked for Legesher (the guard cannot check what it points to; replace it with the file), or a file under .legesher/ that is a symlink or a submodule, that does not match its format (config.yml, metadata/*.yml, translation_memory.json, identifier_map.json), or that records a path outside the repository; metadata.directory must stay under .legesher/, with no ... The directory's name is matched in any letter case. Other files there are never loaded.

Exit status is 0 with no findings, 1 with findings, and 2 when the guard cannot run (outside a repository, or an unknown language).

The hook is a client-side check: git commit --no-verify, merges and rebases skip it, and it lets a commit through with a notice when it cannot find legesher. A pull-request check running legesher guard --diff sees changes made anywhere, edits on github.com included, but it checks a pull request's net change against its base: the commits inside it land as they are under a merge commit or a rebase merge, and a push that skips pull requests is not checked unless branch protection requires one.

In diff mode a file counts as Legesher's when the base's or the head's .gitattributes marks it, and the language comes from the base's .legesher/config.yml (English when the base names none; the pull request's own configuration is never read), so nothing a pull request changes in its tree relaxes what it is judged by. Diff mode also refuses every marked symlink in the tree, and every symlink or submodule under .legesher/, not only the changed ones, since a change elsewhere can use a link already there. A repository that already holds a marked symlink fails every pull request until the link is replaced with the file it points to.

The check's own workflow file is outside what the guard reads: on pull_request, GitHub runs the workflow as the pull request left it, so a pull request could change the step's inputs or remove it while the check keeps its name. Protect .github/workflows/ as well, for example with a code owner and required code-owner review.

legesher config

Manage project and global configuration.

Project configuration is created by legesher init or legesher git setup; config reads and writes it afterwards. get and list report the keys that are actually in the files, so what they show depends on which of those two commands wrote the config (see Configuration).

# Set default language for project
legesher config set language es

# Get the key back
legesher config get language

# List all configuration settings (user-level merged with project)
legesher config list

# Set user-level configuration
legesher config set language es --global

config get exits 1 with Key '<key>' not found. when the key is absent from both files: it does not fall back to the built-in defaults.

Configuration Locations:

  • Project: .legesher/config.yml (current directory)
  • User: ~/.legesher/config.yml (user home directory)

Available Settings (the keys the runtime reads, and the ones legesher git setup writes):

  • language: Default language code (e.g., es, fr, ar)
  • python_version: Target Python version (e.g., 3.12, 3.11), or null to auto-detect
  • git.canonical_language: written as en by legesher init and legesher git setup for compatibility; the filter does not read it, because each file is stored in its author's language
  • git.filter_enabled: Enable git filter integration (boolean)
  • runtime.cache_size: Translation cache size. Honoured when present; neither command writes it out
  • metadata.enabled, metadata.directory, metadata.track_in_git, metadata.verbose_history, metadata.max_history_entries: Metadata tracking

legesher init writes a different set of keys (languages, git.enabled, ai_context.*, translation.*, editor.*). config get and config list will report those verbatim, but nothing in the runtime consults them.

legesher metadata

Inspect the translation metadata Legesher records for the files in your repository. Every subcommand except list takes a file path, not a language code.

# Show metadata for one file
legesher metadata show src/calculator.py

# Show the clean/smudge history for a file
legesher metadata history src/calculator.py --limit 20

# List every file with recorded metadata
legesher metadata list

# Show the raw YAML
legesher metadata raw src/calculator.py

# Check a metadata file is present, valid YAML, and complete
legesher metadata validate src/calculator.py

Every subcommand takes --repo PATH to point at a repository other than the current directory.

To inspect a language pack rather than a file's history, use the legesher-i18n CLI:

legesher-i18n registry list
legesher-i18n validate libs/i18n/language-packs/python/es/legesher_i18n_python_es/packs/3.13.yml

Configuration

The legesher CLI uses YAML configuration files for project and user settings: .legesher/config.yml in a project, ~/.legesher/config.yml for user-wide defaults.

Known issue: legesher init and legesher git setup write two different, incompatible shapes. git setup writes the shape the runtime reads. init writes its own shape, which the runtime does not understand, so after legesher init --language es the working language silently falls back to en: legesher git status reports Language: en, legesher run without --lang fails to compile the file, config get language exits 1, and the git clean filter cannot recognize your language, so it leaves stray keywords as typed. Reconciling the two is tracked as its own issue. Until it lands, run legesher git setup --language <code> after legesher init (it rewrites config.yml in the shape everything else reads, and leaves the generated AI context files alone).

Project configuration written by legesher git setup

This is the shape LegesherConfig loads, and the keys legesher config documents above:

language: es
python_version: null      # null means auto-detect
git:
  canonical_language: en
  filter_enabled: true
metadata:
  enabled: true
  directory: .legesher/metadata
  track_in_git: true
  verbose_history: false
  max_history_entries: 100

Project configuration written by legesher init

Same path, different keys. Nothing in the runtime reads the keys unique to this shape; python_version and git.canonical_language are shared with the shape above and are read:

# Languages enabled in this project
languages:                # a list, not the single `language:` string above
  - es

python_version: "3.13"

git:
  enabled: true           # not git.filter_enabled
  canonical_language: "en"

ai_context:
  enabled: true
  context_files:
    - AI-CONTEXT-es.md

translation:
  default_source: "en"
  default_target: "es"
  preserve_comments: true
  preserve_strings: true

editor:
  lsp_hover: true
  inline_completion: true
  bilingual_tooltips: true

User Configuration

~/.legesher/config.yml holds user-wide defaults and uses the same keys as the git setup shape.

Configuration Precedence

Settings are resolved in this order (highest to lowest priority):

  1. Command-line flags (e.g., --language es)
  2. Project configuration (.legesher/config.yml)
  3. User configuration (~/.legesher/config.yml)
  4. Built-in defaults

Examples

Example 1: Starting a New Spanish Project

# Create project directory
mkdir mi_proyecto
cd mi_proyecto
git init

# Initialize legesher configuration (writes config.yml, git filters, AI context)
legesher init --language es --git --python-version 3.13

# Rewrite config.yml in the shape the runtime reads. Without this the filters are
# installed but inert, and your code is stored in Spanish. See Configuration above
legesher git setup --language es --python-version 3.13

# Create a Python file in Spanish. Keywords and builtins are Spanish;
# your own names, and everything inside strings, stay as you write them
cat > hola.py << 'EOF'
definir saludar(nombre):
    """Saluda a una persona por su nombre."""
    imprimir(f"¡Hola, {nombre}!")

saludar("Mundo")
EOF

git add .
git commit -m "Initial commit"

# The file is stored in Spanish, as written; a stray English keyword would be normalized to Spanish

Example 2: Working in an English Project

# Clone an English Python project
git clone https://github.com/example/python-project.git
cd python-project

# Set up legesher to work in Spanish
legesher git setup --language es

# Translate a throwaway copy into Spanish for reading
legesher translate src/ --to es --output src_es/

# The project's files stay in English, the language their authors wrote them in.
# A Spanish keyword typed into one of them is normalized back to English at git add;
# a new file you write in Spanish is stored in Spanish.

Example 3: Switching Between Languages

# Currently working in Spanish (config written by `legesher git setup --language es`)
legesher config get language
# Output: es

# Translate a copy of the codebase to French, through English (direct pairs are not supported yet)
legesher translate src_es/ --from es --to en --output src_en/
legesher translate src_en/ --to fr --output src_fr/

# Update configuration
legesher config set language fr

# Now working in French!

Example 4: Checking a File's Translation Record

# Show what Legesher has recorded for a file
legesher metadata show src/calculator.py

# Check that record is present and well-formed
legesher metadata validate src/calculator.py

Development

Running Tests

# Run all tests
pytest

# Run with coverage
pytest --cov=legesher --cov-report=html

# Run specific test file
pytest tests/test_cli.py

Project Structure

libs/legesher/
├── legesher/
│   ├── __init__.py
│   ├── config.py           # Configuration loading and precedence
│   ├── cli/
│   │   ├── main.py         # Main CLI entry point
│   │   ├── init.py         # init command
│   │   ├── translate.py    # translate command
│   │   ├── run.py          # run command
│   │   ├── git.py          # git command group
│   │   ├── config_cmd.py   # config command group
│   │   └── metadata.py     # metadata command group
│   └── runtime/
│       ├── import_hook.py  # Import hook for `legesher run`
│       ├── source_mapper.py
│       └── error_handler.py
├── tests/
├── pyproject.toml
└── README.md

Contributing

We welcome contributions! Please see:

License

Apache License 2.0 - see LICENSE for details.


Part of the Legesher Project - Empowering developers to code in their native language.

Metadata

Release files for legesher 0.24.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for legesher 0.24.0
File Size Uploaded Provenance Actions
legesher-0.24.0.tar.gz 142.7 kB With provenance Details for legesher-0.24.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for legesher 0.24.0
File Interpreter ABI Platform Actions
legesher-0.24.0-py3-none-any.whl Python 3 none any Details for legesher-0.24.0-py3-none-any.whl

Total release size: 214.0 kB

Release files / legesher-0.24.0.tar.gz

Download URL legesher-0.24.0.tar.gz
Size 142.7 kB
Tags Source
SHA-256 checksum
How to use checksums
90688f8600c59a0af3f8d35cb6beb57f3cbba5d21de28e3c49be70d1149a4d89
BLAKE2b-256 checksum
How to use checksums
e078622e38edfb23500c41c8015110931aa3b01984de5b503558c36972225d91
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / legesher-0.24.0-py3-none-any.whl

Download URL legesher-0.24.0-py3-none-any.whl
Size 71.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1ccee662589525a5f5a62b40dd98c9201fba485a43b513546a12cca0fe79c435
BLAKE2b-256 checksum
How to use checksums
9a843d05f947d6539e91c3c0e480693cce77dccb19bdc10dbe3e8c230a108312
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Security for legesher 0.24.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for legesher-0.24.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.24.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page