# Add Your Language Anyone can publish a `foreignthon-xx` language pack to PyPI — no access to the core repo is needed, and the pack works for anyone who installs it the moment it is on PyPI. The recommended path is to start from the official language template. See [Contributing → Language Packs](../contributing/language-packs.md) for the full contribution and review process. This page is a quick reference. --- ## Start from the template Fork the official language template on Gitea: ``` https://git.keshavanand.net/foreign-thon/language-template.git ``` The template gives you the full correct structure — `__init__.py`, JSON pack, the universal `tests/test_pack.py`, and all three CI workflows — pre-wired and ready to rename. !!! tip "Why fork instead of starting fresh?" The template includes the shared test suite (`tests/test_pack.py`) that every official pack uses. Starting from it means your CI is already correct and your pack will pass the same checks as `foreignthon-es` and `foreignthon-ta` from day one. Alternatively, if you just want a local pack for your own project without publishing, use: ```bash fpy new myproject --lang custom ``` This scaffolds a `custom.json` locally — no PyPI account needed. See [Custom Packs](../custom-packs.md) for details. --- ## Package structure After renaming the template for your language (e.g. French, code `fr`): ``` foreignthon-fr/ ├── .gitea/workflows/ │ ├── ci.yml # pytest on push / PR │ ├── publish.yml # PyPI upload on v* tag │ └── trigger-docs.yml # docs rebuild on README.md change ├── .gitignore ├── LICENSE # GPL v3 ├── README.md ├── pyproject.toml └── src/ └── foreignthon_fr/ ├── __init__.py └── fr.json ``` --- ## `pyproject.toml` ```toml [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "foreignthon-fr" version = "0.1.0" description = "French language pack for ForeignThon." license = { text = "GPL v3" } requires-python = ">=3.9" authors = [ { name = "Your Name", email = "you@example.com" } ] keywords = ["foreignthon", "french", "français"] dependencies = ["foreignthon>=0.5.3"] [project.urls] Homepage = "https://git.keshavanand.net/foreign-thon/foreignthon-fr" [project.entry-points."foreignthon.langs"] fr = "foreignthon_fr" [tool.hatch.build.targets.wheel] packages = ["src/foreignthon_fr"] ``` The `[project.entry-points."foreignthon.langs"]` block is what makes ForeignThon auto-discover your pack — the key is the language code, the value is the Python module name. --- ## `__init__.py` ```python from importlib.resources import files from importlib.metadata import version, metadata, PackageNotFoundError try: package_name = (__package__ or "").replace("_", "-") __version__ = version(package_name) pkg_metadata = metadata(package_name) raw_authors = pkg_metadata.get_all("Author") or [] raw_emails = pkg_metadata.get_all("Author-email") or [] combined = [] for item in (raw_authors + raw_emails): clean_name = item.split("<")[0].strip() if clean_name and clean_name not in combined: combined.append(clean_name) __authors__ = combined except PackageNotFoundError: __version__ = "0.0.0" __authors__ = [] def get_pack_path(): return files(__name__) / "fr.json" ``` Change `"fr.json"` to match your actual filename. --- ## The JSON pack Keys are your language's words. Values are English Python identifiers — **never change the values**. ```json { "meta": { "name": "French", "native_name": "Français", "code": "fr" }, "keywords": { "si": "if", "sinon": "else", "sinonsi": "elif", "pour": "for", "tantque": "while", "déf": "def", "classe": "class", "importer": "import", "depuis": "from", "comme": "as", "retourner": "return", "arrêter": "break", "continuer": "continue", "passer": "pass", "essayer": "try", "sauf": "except", "finalement": "finally", "lever": "raise", "avec": "with", "dans": "in", "est": "is", "et": "and", "ou": "or", "non": "not", "supprimer": "del", "global": "global", "nonlocal": "nonlocal", "affirmer": "assert", "générer": "yield", "attendre": "await", "asynchrone": "async", "lambda": "lambda", "Vrai": "True", "Faux": "False", "Rien": "None" }, "builtins": { "afficher": "print", "saisir": "input", "longueur": "len", "intervalle": "range", ... }, "exceptions": { "ErreurDeValeur": "ValueError", ... }, "error_messages": { "ValueError": "Erreur de valeur", ... }, "stdlib": { "mathématiques": "math", ... }, "postfix_keywords": [] } ``` ### `postfix_keywords` Set this to a list of English keywords if your language is SOV (subject-object-verb) and benefits from `@@` postfix style on decompile. Example (Tamil): ```json "postfix_keywords": ["if", "elif", "while", "class", "with"] ``` Set it to `[]` for SVO languages like Spanish or French. ### Filename rule The JSON filename **must** match `meta.code` exactly. `fr.json` for code `"fr"`. --- ## Validate and test ```bash pip install -e . pip install pytest fpy pack src/foreignthon_fr/fr.json # ✓ Pack 'French' is valid. pytest tests/ -v ``` The shared `tests/test_pack.py` checks that all sections exist, all values are real Python identifiers, the filename matches `meta.code`, and `postfix_keywords` entries each have a translation. --- ## Publish Tag and push — the CI handles the rest: ```bash git tag v0.1.0 git push origin v0.1.0 ``` The `publish.yml` workflow builds the wheel and uploads it to PyPI using the `PYPI_TOKEN` secret. Add your own `PYPI_TOKEN` to your fork's secrets if you are not yet under the official org. --- ## Getting listed Once your pack is working and on PyPI, open a PR or issue on [foreignthon-docs](https://git.keshavanand.net/foreign-thon/foreignthon-docs) to get added to the [Language Packs overview](index.md). See [Contributing → Language Packs](../contributing/language-packs.md) for the full process.